TSGCTF 2021 | Baba is Flag

#hakatashi

#TSGCTF_2021

require 'openssl'
require 'digest'

STDOUT.sync = true

class OpenSSL::PKey::EC::Point
  def xy
    n = to_bn(:uncompressed).to_i
    mask = (1 << group.degree) - 1
    return (n >> group.degree) & mask, n & mask
  end
  alias_method :+, :add
  alias_method :*, :mul
end

class ECDSA
  def initialize
    @curve = OpenSSL::PKey::EC::Group.new('secp256k1')
    @G = @curve.generator
    @n = @curve.order.to_i
    @d = OpenSSL::BN.rand(@curve.degree).to_i
    @Q = @G * @d
  end

  def inv(x)
    x.pow(@n - 2, @n)
  end

  def sign(msg)
    z = Digest::SHA256.hexdigest(msg).hex
    k = OpenSSL::BN.rand(@curve.degree / 3).to_s.unpack1('H*').hex
    x, y = (@G * k).xy

    # We all like hacks, ain't we?
    # s = (z + x * @d) * inv(k) % @n
    s = (z + @d) * inv(k) % @n

    return x, s
  end

  def verify(msg, x, s)
    return false if x % @n == 0 || s % @n == 0
    z = Digest::SHA256.hexdigest(msg).hex

    # ditto
    # x2, y2 = (@G * (z * inv(s)) + @Q * (x * inv(s))).xy
    x2, y2 = (@G * (z * inv(s)) + @Q * inv(s)).xy

    return x == x2
  end
end

ecdsa = ECDSA.new

5.times do
  puts <<~EOS
    1. Sign
    2. Find rule
    3. Exit
  EOS

  print 'choice? '

  case gets.chomp
  when '1'
    x, s = ecdsa.sign('Baba')
    puts 'Baba is:'
    puts "x = #{x}"
    puts "s = #{s}"
  when '2'
    print 'Which rule do you want to know? '; msg = gets.chomp
    print 'x? '; x = gets.to_i
    print 's? '; s = gets.to_i

    if ecdsa.verify(msg, x, s)
      if msg == 'Baba'
        puts 'Baba is you'
      elsif msg == 'Flag'
        puts "Flag is #{ENV['FLAG']}"
      else
        puts 'Not Found :('
      end
    else
      puts 'Invalid :('
    end
  else
    exit
  end
end

puts 'You is defeat.'

#ECDSA

ただしsign r = kG.x, s = (z + d)k^{-1} \mod nのように、 sの計算に rを使わなくなっている

このとき何が起こるかというと、 rからlift_x すると kGが復元でき、 s*kG = (z + d)G = zG + Qである。

 zは知っているはずなので s*kG - zG = dG Qが導ける

 Qがわかれば署名を偽造できる。 sを適当に固定してしまえば s^{-1}(zG + Q).x == rとなれば良くて、左辺の値は全て知っているから、 rを求めることができる。

from hashlib import sha256
from ptrlib import Socket

p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f
K = GF(p)
a = K(0x0000000000000000000000000000000000000000000000000000000000000000)
b = K(0x0000000000000000000000000000000000000000000000000000000000000007)
E = EllipticCurve(K, (a, b))
G = E(0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8)
E.set_order(0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 * 0x1)

sock = Socket("nc 34.146.212.53 65434")
sock.sendlineafter("choice? ", "1")

# -- find Q
r = int(sock.recvlineafter("x = "))
s = int(sock.recvlineafter("s = "))

z = int(sha256(b"Baba").hexdigest(), 16)
kG = E.lift_x(Integer(r))
Q = s*kG - z*G

# -- forgery
z = int(sha256(b"Flag").hexdigest(), 16)
s = 1
r = (z*G + Q).xy()[0]


sock.sendlineafter("choice? ", "2")
sock.sendlineafter("know? ", "Flag")
sock.sendlineafter("x?", str(r))
sock.sendlineafter("s?", str(s))

sock.interactive()