年賀状CTF 2017 writeup

今年もid:nanuyokakinuさんによる年賀状CTFが開催されていたので参加した.
reversing問題が全部で3問.すべて解くと最後のフラグにAmazonのギフト券が書いてあり,お年玉が貰える.今年はなんと0x1337円.ありがとうございました :)

nanuyokakinu.hatenablog.jp

以下,解いた問題のwriteup.

stage1

% file stage1.exe
stage1.exe: PE32+ executable for MS Windows (console) Mono/.Net assembly

64bitのPE.IDA Pro Freeが使えないのでHopperを使って読んだ.ただし,静的解析の妨害として,文字列がエンコードされているので,x64dbgというデバッガで動かしながら確認していった.

notepad.exeのプロセスのメモリをWriteProcessMemoryで書き換えている.ダンプするとまたPEが出てくるのでそれらしい処理をしているところを見ると,HappyNewYear2017を鍵として暗号化したものがERV5vdff++FakEbRj0z8UyhZPPBYLLPm5xYAeVPPKsGlvRzPH4Bq+o1tZQB2wgznになればよいらしい.
stage1.exeに同様の___ENCRYPTKEY___が鍵の復号処理があるので,それを流用した.

# -*- coding: utf-8 -*-
import base64
import struct

p = lambda x: struct.pack('<I', x)
u = lambda x: struct.unpack('<I', x)[0]
u4 = lambda x: [u(x[i:i+4]) for i in range(0, len(x), 4)]

password = u4(base64.b64decode('ERV5vdff++FakEbRj0z8UyhZPPBYLLPm5xYAeVPPKsGlvRzPH4Bq+o1tZQB2wgzn'))
enckey = u4('HappyNewYear2017')

length = len(password)
blocknum = 52 / length + 6

block = password[0]

i_1 = blocknum * 0x9e3779b9
i_1 &= 0xffffffff

for _ in range(blocknum):
    i_2 = (i_1 >> 2) & 3
    for j in range(length-1, -1, -1):
        c = password[j-1]

        edx = (c >> 5) ^ ((block << 2) & 0xffffffff)
        r8d = (block >> 3) ^ ((c << 4) & 0xffffffff)
        ecx = edx + r8d
        ecx &= 0xffffffff

        edx = block ^ i_1
        r8d = enckey[(j & 3) ^ i_2] ^ c

        edx += r8d
        edx &= 0xffffffff
        ecx ^= edx
        eax = password[j]
        eax -= ecx
        eax &= 0xffffffff
        d = eax

        password[j] = d
        block = d

    i_1 -= 0x9e3779b9
    i_1 &= 0xffffffff

print ''.join([p(x) for x in password])
NYC{L0gg1ng_Cl1pb04rd_w17h_Dll_1nj3c710n})

notepad.exeに対して,dll injectionを行い,その中でクリップボードのデータからフラグチェックを行うものだったらしい.

stage2

WebAssembly!! wasmファイルが同梱されていて,フラグチェックがWebAssembly上で行われる.ブラウザ上で動かす場合,Chromeならchrome://flags/#enable-webassemblyから有効にできる.

まずはwasmをwastに変換して読み始める. https://github.com/WebAssembly/wabt にあるwasm2wastを使うと変換できる.命令セットについては, https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md を参照.

main関数まわりを読んでみると,スタックに引数を積んでいきながら命令を実行していくスタックマシンのようなかんじ.ローカル変数は関数に渡された引数,関数内で使っている変数という順番に番号が振られる.

stage2.htmlを見ると,main関数にコマンドライン引数として比較される文字列を渡している. main(func 28)を見ると,2つの引数argc, argvを持っていることが分かる.コード中に*(argv+4)のような処理があるので合っているはず.

  (func (;28;) (type 1) (param i32 i32) (result i32)
    (local i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32)
    block i32  ;; label = @1
      get_global 9
      set_local 47
      get_global 9
      i32.const 32
      i32.add
      set_global 9
      get_global 9
      get_global 10
      i32.ge_s
      if  ;; label = @2
        i32.const 32
        call 3
      end
      i32.const 0
      set_local 12
      get_local 0
      set_local 23
      get_local 1
      set_local 34
      get_local 34
      set_local 44
      get_local 44
      i32.const 4
      i32.add
      set_local 45
      get_local 45
      i32.load
      set_local 2
      get_local 2
      call 41
      set_local 3
      get_local 3
      set_local 42
      get_local 42
      set_local 4
      get_local 4
      i32.const 46
      i32.ne
      set_local 5
      get_local 5
      i32.eqz
      if  ;; label = @2
        get_local 42
        set_local 6
        get_local 6
        i32.const 1
        i32.add
        set_local 7
        get_local 7
        i32.const 1
        call 52
        set_local 8
        get_local 8
        set_local 41
        get_local 41
        set_local 9
        get_local 34
        set_local 10
        get_local 10
        i32.const 4
        i32.add
        set_local 11
        get_local 11
        i32.load
        set_local 13
        get_local 9
        get_local 13
        call 42
        drop
        i32.const 0
        set_local 43
        loop  ;; label = @3
          block  ;; label = @4
            get_local 43
            set_local 14
            get_local 42
            set_local 15
            get_local 14
            get_local 15
            i32.lt_u
            set_local 16
            get_local 16
            i32.eqz
            if  ;; label = @5
              br 1 (;@4;)
            end
            get_local 43
            set_local 17
            get_local 41
            set_local 18
            get_local 18
            get_local 17
            i32.add
            set_local 19
            get_local 19
            i32.load8_s
            set_local 20
            get_local 20
            i32.const 255
            i32.and
            set_local 21
            get_local 21
            i32.const 255
            i32.xor
            set_local 22
            get_local 22
            i32.const 255
            i32.and
            set_local 24
            get_local 19
            get_local 24
            i32.store8
            get_local 43
            set_local 25
            get_local 41
            set_local 26
            get_local 26
            get_local 25
            i32.add
            set_local 27
            get_local 27
            i32.load8_s
            set_local 28
            get_local 28
            call 27
            set_local 29
            get_local 29
            call 3
            get_local 43
            set_local 30
            get_local 41
            set_local 31
            get_local 31
            get_local 30
            i32.add
            set_local 32
            get_local 32
            get_local 29
            i32.store8
            get_local 43
            set_local 33
            get_local 33
            i32.const 1
            i32.add
            set_local 35
            get_local 35
            set_local 43
            br 1 (;@3;)
          end
        end
        get_local 41
        set_local 36
        get_local 42
        set_local 37
        get_local 36
        i32.const 1144
        get_local 37
        call 37
        set_local 38
        get_local 38
        i32.const 0
        i32.ne
        set_local 39
        get_local 39
        i32.eqz
        if  ;; label = @3
          i32.const 1195
          call 49
          drop
          i32.const 1
          set_local 12
          get_local 12
          set_local 40
          get_local 47
          set_global 9
          get_local 40
          return
        end
      end
      i32.const 1191
      call 49
      drop
      i32.const 0
      set_local 12
      get_local 12
      set_local 40
      get_local 47
      set_global 9
      get_local 40
      return
    end)

これだと読みにくいので,擬似コードに直すスクリプトを書いて変換して読んだ.

@1: {
  l[47] = g[9]
  g[9] = g[9] + 32
  if g[9] >= g[10] {
    call 3(32)
  }
  l[12] = 0
  l[23] = l[0]
  l[34] = l[1]
  l[44] = l[34]
  l[45] = l[44] + 4
  l[2] = *l[45]
  call 41(l[2]) # strlen
  l[3] = RETVAL
  l[42] = l[3]
  l[4] = l[42]
  l[5] = l[4] != 46 # 入力は46文字
  if l[5] == 0 {
    l[6] = l[42]
    l[7] = l[6] + 1
    call 52(l[7], 1) # バッファの確保?
    l[8] = RETVAL
    l[41] = l[8]
    l[9] = l[41]
    l[10] = l[34]
    l[11] = l[10] + 4
    l[13] = *l[11]
    call 42(l[9], l[13]) # 確保したバッファに文字列をコピー?
    drop
    l[43] = 0
    loop@3: {
      @4: {
        l[14] = l[43]
        l[15] = l[42]
        l[16] = l[14] < l[15]
        if l[16] == 0 {
          br (;@4;)
        }
        l[17] = l[43]
        l[18] = l[41] # 渡された文字列
        l[19] = l[18] + l[17]
        l[20] = *l[19]
        l[21] = l[20] & 255
        l[22] = l[21] ^ 255
        l[24] = l[22] & 255
        *l[19] = l[24]
        l[25] = l[43]
        l[26] = l[41]
        l[27] = l[26] + l[25]
        l[28] = *l[27]
        call 27(l[28]) # 変換
        l[29] = RETVAL
        l[30] = l[43]
        l[31] = l[41]
        l[32] = l[31] + l[30]
        *l[32] = l[29]
        l[33] = l[43]
        l[35] = l[33] + 1
        l[43] = l[35]
        br (;@3;)
      }
    }
    l[36] = l[41]
    l[37] = l[42]
    # 
    call 37(l[36], 1144, l[37]) # strncmp
    l[38] = RETVAL
    l[39] = l[38] != 0
    if l[39] {
      call 49(1195) # good
      drop
      l[12] = 1
      l[40] = l[12]
      g[9] = l[47]
      return l[40]
    }
  }
  call 49(1191) # bad
  drop
  l[12] = 0
  l[40] = l[12]
  g[9] = l[47]
  return l[40]
}

func 27:

@1: {
  l[8] = g[9]
  g[9] = g[9] + 16
  if g[9] >= g[10] {
    call 3(16)
  }
  l[1] = l[0]
  l[2] = l[1]
  call 26(l[2], 12, 2)
  l[3] = RETVAL
  l[1] = l[3]
  l[4] = l[1]
  call 26(l[4], 34, 1)
  l[5] = RETVAL
  l[1] = l[5]
  l[6] = l[1]
  g[9] = l[8]
  return l[6]
}

func 26:

@1: {
  l[31] = g[9]
  g[9] = g[9] + 16
  if g[9] >= g[10] {
    call 3(16)
  }
  l[23] = l[0]
  l[24] = l[1]
  l[25] = l[2]
  l[27] = l[23]
  l[28] = l[27] & 255
  l[29] = l[25]
  l[3] = l[28] >> l[29]
  l[4] = l[23]
  l[5] = l[4] & 255
  l[6] = l[3] ^ l[5]
  l[7] = l[24]
  l[8] = l[7] & 255
  l[9] = l[6] & l[8]
  l[10] = l[9] & 255
  l[26] = l[10]
  l[11] = l[23]
  l[12] = l[11] & 255
  l[13] = l[26]
  l[14] = l[13] & 255
  l[15] = l[12] ^ l[14]
  l[16] = l[26]
  l[17] = l[16] & 255
  l[18] = l[25]
  l[19] = l[17] << l[18]
  l[20] = l[15] ^ l[19]
  l[21] = l[20] & 255
  l[23] = l[21]
  l[22] = l[23]
  g[9] = l[31]
  return l[22]
}

後半に表われる1144や1195, 1191はデータを参照している.

  (data (i32.const 1024) "\04\04\00\00\05\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00\03\00\00\00\d8\06\00\00\00\04\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\00\00\0a\ff\ff\ff\ff\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\04\04\00\00\8b\9c\da\90\c8\f0\d3\e5\d0\d0\f0\86\d3\87\94\88\e5\83\c7\88\87\87\c1\86\88\e5\d1\f0\88\c9\f0\d1\94\88\f4\83\e0\f0\d1\f0\e4\e0\f4\83\c2\84\00bad\00good"))

手軽にデバッグするがないか探していたら,importされているabortStackOverflowを使うとabortのメッセージで値をリークできた.

f:id:akiym:20170103100438p:plain

例えば,ローカル変数28が文字列への参照だったときに,その先頭1バイトを確認する場合は以下のコードを追加して,wast2wasmでwasmに変換して実行する.

get_local 28
i32.load8_u
call 3

入力を変換した後の結果が\x8b\x9c\xda\x90\xc8\xf0\xd3\xe5\xd0\xd0\xf0\x86\xd3\x87\x94\x88\xe5\x83\xc7\x88\x87\x87\xc1\x86\x88\xe5\xd1\xf0\x88\xc9\xf0\xd1\x94\x88\xf4\x83\xe0\xf0\xd1\xf0\xe4\xe0\xf4\x83\xc2\x84になれば正解.
1文字ずつ総当たりして求めた.

# -*- coding: utf-8 -*-

def func26(c, x, y):
    A = ((c >> y) ^ c) & x
    return (c ^ A) ^ (A << y) & 0xff

def func27(c):
    return func26(func26(c, 12, 2), 34, 1)

answer = '\x8b\x9c\xda\x90\xc8\xf0\xd3\xe5\xd0\xd0\xf0\x86\xd3\x87\x94\x88\xe5\x83\xc7\x88\x87\x87\xc1\x86\x88\xe5\xd1\xf0\x88\xc9\xf0\xd1\x94\x88\xf4\x83\xe0\xf0\xd1\xf0\xe4\xe0\xf4\x83\xc2\x84'

flag = ''
for i in range(46):
    for c in range(0x20, 0x7f):
        if (ord(answer[i])) == func27(c ^ 0xff):
            flag += chr(c)
            break
print flag
NYC{W3b4ss3mbly_4nd_llvm_4r3_V3ry_1n73r3571ng}

stage3

dlangバイナリ.Hopperにはdlangのdemangle機能がないので,objdump --demangle=dlang --sym stage3の結果を見ながら解析する.
stage1と同様に,この問題でも文字列がエンコードされているので,デバッガで動かしながら確認していくとhttps://userstream.twitter.com/1.1/user.jsonという文字列やstage3.send_dmという関数があるので,Twitterに関連した問題だと分かる.

DMを経由してコマンドを実行するC2サーバのような動作をしている.
.dataセクションにOAuthのconsumer keyとaccess tokenだけではなく,consumer secretとaccess token secretが残っているのでこれを利用すると,@mytyl_nyctfのDMの内容を見ることができる.@tyltyl_nyctfとDMでやりとりされており,その内容がコマンドの実行結果となっている.

コマンド,実行結果は以下の手順で暗号化されている.

  • zlibで圧縮
  • RC4で暗号化 (key=Thank you for playing! Almost there!)
  • カスタムテーブルを持ったBase64エンコード (table=TXyU9lM5VfHkRS0YgvK4hcnb~CEIFBQ7r3zdAqO6DNe2p8sxmtL_JuGa1joZWiP-w)

逆の手順を行うことで復号することができる.カスタムBase64RC4というとDaserfっぽい.

import base64
import string
import zlib
from Crypto.Cipher import ARC4

transtable = string.maketrans(
    'TXyU9lM5VfHkRS0YgvK4hcnb~CEIFBQ7r3zdAqO6DNe2p8sxmtL_JuGa1joZWiP-w',
    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
)
rc4_key = 'Thank you for playing! Almost there!'

enc = [
    'D7puoyfhNkq6zCko6gRw',
    'D78ZUUn0eWXadhnHJp3tF7E_3Yg~oAvG~jY6f2nWK8nyixt-Ax8~ifbWeEgP7V1W1Q0lbvYmZaIIX1_x7UhB9tdzgVIHtUeQbNjODzO15A45sNVlKMN5jPZ9Ra30l6D5LOyUv_6I5y1lcglgk4PH1U3TFpZE',

    'D7suzudHbRRLnhE1cZTeZxcnPWf8Xu1Ynmww',
    open('encflag', 'r').read(),
]

for x in enc:
    x = x.translate(transtable)
    x = base64.b64decode(x)
    x = ARC4.new(rc4_key).decrypt(x)
    x = zlib.decompress(x)
    print x
ls -la
total 5152
drwxr-xr-x 1 vagrant vagrant     136 Dec 31 16:37 .
drwxr-xr-x 1 vagrant vagrant     306 Dec 31 16:28 ..
-rw-r--r-- 1 vagrant vagrant    6980 Dec 31 16:33 flag.jpg
-rwxr-xr-x 1 vagrant vagrant 5264181 Dec 31 16:09 stage3

base64 ./flag.jpg
/9j/4AAQSkZJRgABAQEASABIAAD/4QCMRXhpZgAATU0AKgAAAAgABgEGAAMAAAABAAIAAAESAAMA
...

デコードするとギフト券番号が書いてある画像が出てくる.