binary 200
injector.exeをデバッガで開いて実行しながら処理を追いかけると、notepad.exeのプロセスを探している。
notepad.exeを起動してから実行してみると、
64bitの場合ZwQueryInformationProcess関数でPebBaseAddressが正しく取得できず失敗する模様。
適当な32bitプログラムをnotepad.exeにリネームしてデバッガから起動。
再度injector.exeを実行すると、WriteProcessMemoryまでたどり着く。
notepad.exeに戻って、書き込み先アドレスへ移動し実行する。
引数が3つあるようなので、スタックを適当に減らして0クリアしておく。
ステップ実行するとメモリアクセスエラーが発生するが、適当に処理をスキップしながら実行。
push命令の羅列が終わったところで
GZPGS{jnag_fhz_vng_ubbxvat}
という文字列が見えるのでrot13して終了。
TMCTF{want_sum_iat_hooking}
binary 300
最初にアンパック処理が入るので、JMP先の最後のRETN命令(0x4001FA)にブレークポイントを仕掛けて処理を進める。
0x40139Bの関数内でkeyの判定を行っている。
0x40157Fの関数呼び出しで時刻を取得し、0x401624~の分岐で時刻が5~6時台でない場合に弾いている。
分岐の判定を毎回変えるのが面倒なのでPCの時計を変更。
つづいて、0x401673~0x4016A4のループで引数文字列のASCII値の合計を求め、直後にその値が0x1F4であることを確認。
その後、key文字列の中の文字を探している。
順に並べると _ _ _ _ 1 G だが、毎回引数の先頭から探しているので _ 4回の探索は意味が無いように見える。
ただ、気にせずに6文字を繋げると合計は0x1F4になって正解。少し処理を進めると、フラグ文字列が生成される。
TMCTF{F14g1s::____1G}
other 200
適当なソフト(UPXF)でUPXを解凍する。
実行すると
Flag not captured :(
と表示される
デバッガでステップ実行して表示している場所を見つける。
-> 0x407F48
-> 0x406043
分岐で文字列を変更しているかもと思い、その前辺りの0x406005にブレークポイントを設定して実行する。
すると、0x406012でJ`mk,bcx,om|xy~ih,6$
という怪しげな文字列へのポインタをeaxにセットしている。
ダンプ画面でポインタの先を参照すると、その少し前に別の文字列OXJwY\T< Y\T=q
が見える。
eaxの参照先の値をそちらに変えてみると、無事フラグが表示された。
TMCTF{UPX0,UPX1}
other 400
Pythonのバイトコードを解析する問題。
disモジュールで逆アセンブル可能だが、読むのが大変そうだったので uncompyle6 をインストールして関数をデコンパイルした。
そのままだとdecompile関数がexportされていないので以下を追記する。
from uncompyle6.main import decompile
また、UnicodeDecodeError が出るので、以下のファイルを作成する。
import sys
sys.setdefaultencoding('utf-8')
次に、parseltongue.py に以下を追記して、verify_flag関数を直接デコンパイルする。
import uncompyle6
from xdis.load import load_module
def verify_flag(flag): pass
verify_flag.__code__ = verify_flag.__code__.__class__ (略)
uncompyle6.decompile(2.7, verify_flag.__code__, open('flag.py', 'wb'))
(以下略)
verify_flag関数の中身が判明したので、フラグ文字列を逆算する。
処理の内容から、flagの長さは24文字(TMCTF{}を除く)で、値の総和などのチェックを行った後、10個の配列を使って足し算とxorを行っている。
flagのどの文字がどの値に影響しているか調べるために、足し算とxorの代わりに "+a" "^b" といった文字列を繋いでいく処理に変更する。
abcdefghijklmnopqrstuvwx をフラグにして、各配列にどの値が足されているかを確認する。
10個の配列の正解の値は、verify_flagの最後のif文の文字列をmap(ord, '.....')すればよい。
あとは連立一次方程式を解くのと同じ要領で1文字ずつ決定していく。
(例えば、QQRTW[0] は +a を4回行った結果が236になる必要があるため a = 59)
・・・が、文字数が多く面倒なので、以下のようなプログラムで判明した文字を消去して式を簡略化しながら解いた。
results = {
"a" => 59,
"b" => nil,
(snip)
"x" => nil,
}
f = {
"KYRYF" => [['^x^w^v^u', '^w^v^u^t', '^v^u^t^s', '^u^t^s^r', '^t^s^r^q'], [22, 1, 18, 4, 36] ],
"KYRYG" => [['^p^p^p^p', '^p^o^n^m', '^p^n^l^j', '^p^m^j^g', '^p^l^h^d'], [0, 45, 18, 51, 11] ],
"KYRYH" => [['^i^i^i^i', '^i^j^k^l', '^i^k^m^o', '^i^l^o^r', '^i^m^q^u'], [0, 44, 19, 16, 58] ],
"KYRYJ" => [['^a^a^a^a', '^a^b^c^d', '^a^c^e^g', '^a^d^g^j', '^a^e^i^m'], [0, 50, 48, 5, 33] ],
"KYRYK" => [['^a^b^c^d', '^b^c^d^e', '^c^d^e^f', '^d^e^f^g', '^e^f^g^h'], [50, 9, 0, 11, 22] ],
"QQRTQ" => [['+a+b+c+d', '+b+c+d+e', '+c+d+e+f', '+d+e+f+g', '+e+f+g+h'], [108, 49, 58, 67, 40] ],
"QQRTW" => [['+a+a+a+a', '+a+b+c+d', '+a+c+e+g', '+a+d+g+j', '+a+e+i+m'], [236, 108, 102, 169, 93]],
"QQRTE" => [['+i+i+i+i', '+i+j+k+l', '+i+k+m+o', '+i+l+o+r', '+i+m+q+u'], [24, 98, 43, 46, 118] ],
"QQRTR" => [['+p+p+p+p', '+p+o+n+m', '+p+n+l+j', '+p+m+j+g', '+p+l+h+d'], [52, 101, 156, 123, 69] ],
"QQRTY" => [['+x+w+v+u', '+w+v+u+t', '+v+u+t+s', '+u+t+s+r', '+t+s+r+q'], [92, 79, 96, 82, 114] ],
}
f.each do |key, v|
mode = nil
v[0].each_with_index do |op, i|
newop = ""
op.split(//).each do |c|
case c
when "+", "^"
mode = c
else
if results[c]
case mode
when "+"
v[1][i] -= results[c]
when "^"
v[1][i] ^= results[c]
end
else
newop += "#{mode}#{c}"
end
end
end
v[0][i] = newop
end
loop do
flag = false
v[0].each_with_index do |op, i|
if v[0][i] == ""
v[0].delete_at(i)
v[1].delete_at(i)
flag = true
break
end
end
break unless flag
end
puts "\"#{key}\" => [" + f[key][0].to_s + ", " + f[key][1].to_s + "],"
end
s, t, w, x が確定しないが、4パターンしかないので全て表示すると、
s=28, t=15, w=11, x=28
TMCTF{SlytherinPastTheRetgrsct}
s=29, t=14, w=12, x=27
TMCTF{SlytherinPastTheReufrsds}
s=30, t=13, w=13, x=26
TMCTF{SlytherinPastTheReverser}
s=31, t=12, w=14, x=25
TMCTF{SlytherinPastTheRewdrsfq}
となり、英単語が正しい3番目が正解。
TMCTF{SlytherinPastTheReverser}