QR200 - QR (Easy)

Funniest joke in the world(?):
“Last night, I had a dream I was eating QR cakes….
but when I woke up, half my QR code was gone!”

The right half of a pancake on which a QR code is printed


世界一面白いジョーク:
昨晩フランネルケーキを食べる夢を見たんだけど、
朝起きたらQRコードが半分なくなってたんだ!

First Look

At first, we need clean image based on the image above.

The right half of a QR code

The size of this QR code is 29x29 and the size of version V is N × N with N = 17 + 4V, so this is version 3.

Format Information

The right half of a QR code with the format information area highlighted

Note that the area indicates format information of QR code. Actually the format information is 15 bits long and the area has last 8 bits.

The upper right corner of a QR code with format information bits of 10111110

Searching the list of all format information strings, we can find out that the type information bits are 001001110111110. So this QR code has ECC level H and mask pattern 1.

Off the Mask

According to QR Mask Patterns Explained, mask number 1 has formula (row) mod 2 == 0. Note that the row number start from 0. So we have to switch the bit of the row of which the coordinate is 0, 2, 4, …, 28.

Also there are fixed patterns in QR code, so we have to switch bits of data section only. See the data area and the bit order.

QR Ver3 Codeword Ordering
(via wikipedia.org)

So, the raw D1–D26 are:

D1  = 0b11101100
D14 = 0b10000010
D2  = 0b11111000
D15 = 0b10010101
D3  = 0b00110110
D16 = 0b00111101
D4  = 0b01110110
D17 = 0b01100010
D5  = 0b00100010
D18 = 0b11101001
D6  = 0b11110001
D19 = 0b10100001
D7  = 0b00110111
D20 = 0b11100101
D8  = 0b01010010
D21 = 0b11010101
D9  = 0b00010111
D22 = 0b00101101
D10 = 0b11011110
D23 = 0b10010111
D11 = 0b01000100
D24 = 0b10001011
D12 = 0b01010100
D25 = 0b01111000
D13 = 0b11001101
D26 = 0b11000110

After masking off,

D1  = 0b00100000
D14 = 0b01001110
D2  = 0b00110100
D15 = 0b01011001
D3  = 0b11111010
D16 = 0b00001110
D4  = 0b01000101
D17 = 0b01010001
D5  = 0b00010001
D18 = 0b11011010
D6  = 0b00111101
D19 = 0b10010010
D7  = 0b00000100
D20 = 0b11010101
D8  = 0b10011110
D21 = 0b00011001
D9  = 0b11010100
D22 = 0b00010001
D10 = 0b00010100
D23 = 0b00001110
D11 = 0b11011101
D24 = 0b00010010
D12 = 0b11010010
D25 = 0b00011111
D13 = 0b01010100
D26 = 0b01000000

Now we can start decoding the data.

Data Decoding

There are mode indicators for decoding:

  • 0001: Numeric Mode (10 bits per 3 digits)
  • 0010: Alphanumeric Mode (11 bits per 2 characters)
  • 0100: Byte Mode (8 bits per character)
  • 1000: Kanji Mode (13 bits per character)
  • 0111: ECI Mode

Character count indicator follows after a mode indicator.

  • Version 1–9
    • Numeric mode: 10 bits
    • Alphanumeric mode: 9 bits
    • Byte mode: 8 bits
    • Kanji mode: 8 bits
  • Version 10–26
    • Numeric mode: 12 bits
    • Alphanumeric mode: 11 bits
    • Byte mode: 16 bits
    • Kanji mode: 10 bits
  • Version 27–40
    • Numeric mode: 14 bits
    • Alphanumeric mode: 13 bits
    • Byte mode: 16 bits
    • Kanji mode: 12 bits

See the encoding process for each mode:

Let’s start with above data D1–D26:

data = '00100000' \
       '00110100' \
       '11111010' \
       '01000101' \
       '00010001' \
       '00111101' \
       '00000100' \
       '10011110' \
       '11010100' \
       '00010100' \
       '11011101' \
       '11010010' \
       '01010100' \
       '01001110' \
       '01011001' \
       '00001110' \
       '01010001' \
       '11011010' \
       '10010010' \
       '11010101' \
       '00011001' \
       '00010001' \
       '00001110' \
       '00010010' \
       '00011111' \
       '01000000'
alphanumeric = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'.chars

def read(str, size)
  str.slice!(0, size)
end

def kanji(num)
  if num >= 0x1740
    (0xC140 + num / 0xC0 * 0x100 + num % 0xC0)
      .chr(Encoding::Shift_JIS).encode(Encoding::UTF_8)
  else
    (0x8140 + num / 0xC0 * 0x100 + num % 0xC0)
      .chr(Encoding::Shift_JIS).encode(Encoding::UTF_8)
  end
end

loop do
  case mode = read(data, 4)
  when '0010' # Alphanumeric
    count = read(data, 9).to_i(2)
    (count / 2).times do
      chunk = read(data, 11).to_i(2)
      print alphanumeric[chunk / 45] + alphanumeric[chunk % 45]
    end
    print alphanumeric[read(data, 11).to_i(2)] if count.odd?
  when '0100' # Byte
    count = read(data, 8).to_i(2)
    count.times do
      print read(data, 8).to_i(2).chr
    end
  when '1000' # Kanji
    count = read(data, 8).to_i(2)
    count.times do
      print kanji(read(data, 13).to_i(2))
    end
  when '0000' # Terminate
    break
  else
    fail "Unhandled mode #{mode}"
  end
end

Fortunately, the QR code contains alphanumeric encoding and byte encoding only. You can get the flag by running above code with Ruby:

SECCON{PSwIQ9d9GjKTdD8H}

References