├── .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 |
7 |
8 | Level 1 9 |
10 | 11 |
12 |
13 |
14 | 15 |
16 |
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 | ![this diagram](shattered.png). 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 | ![hello](https://i.imgur.com/W9BxK6O.png) 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 | ![](https://i.imgur.com/v0O5BVe.png) 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 | ![initial form](images/initial.png) 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 | ![done](images/done.png) 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 | ![the six faces](images/singmaster.jpg) 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 | ![flag](https://github.com/TechSecCTF/writeups/blob/master/plaidctf2016/plane_site/plane_site_flag.png) 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 | ![flag](https://github.com/TechSecCTF/writeups/blob/master/plaidctf2016/thestuff/flag.jpg) 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 | ![pykemon1](pykemon1.png) 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 | ![pykemon1](pykemon2.png) 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 | ![zip header](zip_header.png) 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 | --------------------------------------------------------------------------------