#!/usr/bin/env ruby require 'pwn' $s = Sock.new('crypto.chal.csaw.io', 1003) $s.recvuntil(':').split("\n")[0] B = 16 def hexdecode(string) [string].pack('H*').bytes end def get_ciphertext(input) puts "Trying #{input}" $s.sendline(input) $s.recvline hexdecode($s.recvline.chomp) end def duplicate_blocks(ciphertext) blocks = ciphertext.each_slice(B) blocks.each_with_index do |block, i| return i if blocks.select { |b| b == block }.length > 1 end false end def guess_prefix_size len = 0 glue = 'X' * 32 loop do i = duplicate_blocks(get_ciphertext('A' * len + glue)) return B * i - len if i len += 1 end end def guess_suffix_size(prefix_size) input = 'X' * 16 old_len = get_ciphertext(input).length loop do input += 'X' new_len = get_ciphertext(input).length return new_len - B - prefix_size - input.length if new_len > old_len end end prefix_size = guess_prefix_size glue_size = B - (prefix_size % B) suffix_size = guess_suffix_size(prefix_size) puts "Guessed prefix size: #{prefix_size}" puts "Guessed glue size: #{glue_size}" puts "Guessed suffix size: #{suffix_size}" def make_short_blocks(prefix_size, glue_size, known) block_count = ((known.length + 1) / B.to_f).ceil block = 'A' * (block_count * B - known.length - 1) glue = 'X' * glue_size ciphertext = get_ciphertext(glue + block) ciphertext.drop(prefix_size + glue_size).take(B * block_count) end def find_in_dict(prefix_size, glue_size, known, short_blocks) block_count = ((known.length + 1) / B.to_f).ceil block = 'A' * (block_count * B - known.length - 1) glue = 'X' * glue_size (32..126).reverse_each do |b| ciphertext = get_ciphertext(glue + block + known + b.chr) match = ciphertext.drop(prefix_size + glue_size).take(B * block_count) padding = ciphertext[-B..-1] p [b.chr, padding] return b.chr if match == short_blocks end raise ':<' end known = '' suffix_size.times do blocks = make_short_blocks(prefix_size, glue_size, known) puts "Created blocks" known += find_in_dict(prefix_size, glue_size, known, blocks) puts "Guessed: #{known}" end $s.close