こんとろーるしーこんとろーるぶい

週末にカチャカチャッターン!したことを貼り付けていくブログ

SECCON Beginners CTF 2018 - Activation

問題文

この問題の FLAG は ctf4b{アクティベーションコード} です。
添付ファイル:Activation_492f6d44cb836cf2cd9279ff3f51d5adc1e132d8.zip

writeup

展開するとexeファイル。実行してみる。 f:id:graneed:20180527192545p:plain

Nextボタンを押下すると強制終了。

fileコマンドで調べると、.Netの実行ファイルであるとわかる。

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

dnSpyで解析する。
github.com

以下のことがわかる。

  • 画面で入力した文字列をAESで暗号化している。
  • 暗号化のキーとIVは実行ファイル内に保有している。
  • 暗号化したデータをBASE64エンコードし、
    実行ファイル内に保有している文字列と突き合わせしている。

暗号化キーとIVと突き合わせ文字列を確認するため、
dnSpyでデコンパイルされたソースのうち、定数クラスを特定する。
ただ、定数としてそのまま定義されておらず、byte配列から切り出すコードになっていた。
定数の値を出力するよう改造した上で、実行して確認する。

実行したソースはこちら。

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

namespace aaaa
{
    // Token: 0x02000009 RID: 9
    [StructLayout(LayoutKind.Auto, CharSet = CharSet.Auto)]
    public class Hoge
    {
        // Token: 0x06000026 RID: 38 RVA: 0x00002584 File Offset: 0x00000784
        private static string get(int A_0, int A_1, int A_2)
        {
            string @string = Encoding.UTF8.GetString(Hoge.raw, A_1, A_2);
            Hoge.datacache[A_0] = @string;
            return @string;
        }

        // Token: 0x06000027 RID: 39 RVA: 0x000025AC File Offset: 0x000007AC
        public static string A()
        {
            return Hoge.datacache[0] ?? Hoge.get(0, 0, 15);
        }

        // Token: 0x06000028 RID: 40 RVA: 0x000025C2 File Offset: 0x000007C2
        public static string a()
        {
            return Hoge.datacache[1] ?? Hoge.get(1, 15, 35);
        }

        // Token: 0x06000029 RID: 41 RVA: 0x000025D9 File Offset: 0x000007D9
        public static string B()
        {
            return Hoge.datacache[2] ?? Hoge.get(2, 50, 35);
        }

        // Token: 0x0600002A RID: 42 RVA: 0x000025F0 File Offset: 0x000007F0
        public static string b()
        {
            return Hoge.datacache[3] ?? Hoge.get(3, 85, 18);
        }

        // Token: 0x0600002B RID: 43 RVA: 0x00002607 File Offset: 0x00000807
        public static string C()
        {
            return Hoge.datacache[4] ?? Hoge.get(4, 103, 8);
        }

        // Token: 0x0600002C RID: 44 RVA: 0x0000261D File Offset: 0x0000081D
        public static string c()
        {
            return Hoge.datacache[5] ?? Hoge.get(5, 111, 16);
        }

        // Token: 0x0600002D RID: 45 RVA: 0x00002634 File Offset: 0x00000834
        public static string D()
        {
            return Hoge.datacache[6] ?? Hoge.get(6, 127, 3);
        }

        // Token: 0x0600002E RID: 46 RVA: 0x0000264A File Offset: 0x0000084A
        public static string d()
        {
            return Hoge.datacache[7] ?? Hoge.get(7, 130, 21);
        }

        // Token: 0x0600002F RID: 47 RVA: 0x00002664 File Offset: 0x00000864
        public static string E()
        {
            return Hoge.datacache[8] ?? Hoge.get(8, 151, 5);
        }

        // Token: 0x06000030 RID: 48 RVA: 0x0000267D File Offset: 0x0000087D
        public static string e()
        {
            return Hoge.datacache[9] ?? Hoge.get(9, 156, 29);
        }

        // Token: 0x06000031 RID: 49 RVA: 0x00002699 File Offset: 0x00000899
        public static string F()
        {
            return Hoge.datacache[10] ?? Hoge.get(10, 185, 64);
        }

        // Token: 0x06000032 RID: 50 RVA: 0x000026B5 File Offset: 0x000008B5
        public static string f()
        {
            return Hoge.datacache[11] ?? Hoge.get(11, 249, 10);
        }

        // Token: 0x06000033 RID: 51 RVA: 0x000026D1 File Offset: 0x000008D1
        public static string G()
        {
            return Hoge.datacache[12] ?? Hoge.get(12, 259, 11);
        }

        // Token: 0x06000034 RID: 52 RVA: 0x000026ED File Offset: 0x000008ED
        public static string g()
        {
            return Hoge.datacache[13] ?? Hoge.get(13, 270, 27);
        }

        // Token: 0x06000035 RID: 53 RVA: 0x00002709 File Offset: 0x00000909
        public static string H()
        {
            return Hoge.datacache[14] ?? Hoge.get(14, 297, 37);
        }

        // Token: 0x06000036 RID: 54 RVA: 0x00002725 File Offset: 0x00000925
        public static string h()
        {
            return Hoge.datacache[15] ?? Hoge.get(15, 334, 11);
        }

        // Token: 0x06000037 RID: 55 RVA: 0x00002744 File Offset: 0x00000944
        static Hoge()
        {
            // Note: this type is marked as 'beforefieldinit'.
            Hoge.raw = new byte[]
            {
                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,
                byte.MaxValue,
                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,
                byte.MaxValue,
                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
            };
            for (int i = 0; i < Hoge.raw.Length; i++)
            {
                Hoge.raw[i] = (byte)((int)Hoge.raw[i] ^ i ^ 170);
            }
        }

        // Token: 0x0400000F RID: 15
        internal static byte[] raw;

        // Token: 0x04000010 RID: 16
        internal static string[] datacache = new string[16];
        
        public static void Main()
        {
            Console.WriteLine("A=" + A());
            Console.WriteLine("a=" + a());
            Console.WriteLine("B=" + B());
            Console.WriteLine("b=" + b());
            Console.WriteLine("C=" + C());
            Console.WriteLine("c=" + c());
            Console.WriteLine("D=" + D());
            Console.WriteLine("d=" + d());
            Console.WriteLine("E=" + E());
            Console.WriteLine("e=" + e());
            Console.WriteLine("F=" + F());
            Console.WriteLine("f=" + f());
            Console.WriteLine("G=" + G());
            Console.WriteLine("g=" + g());
            Console.WriteLine("H=" + H());
            Console.WriteLine("h=" + h());

        }
    }
}

環境用意が面倒なのでオンライン実行可能な.NET Fiddleを利用する。
dotnetfiddle.net

実行結果は以下の通り。.NET Fiddle上で実行可能なリンクも載せておく。
https://dotnetfiddle.net/7vFygo

A=MainWindow.xaml
a=/Activation;component/inputbox.xaml
B=Click "Next" to start activation.


b=Check the disk...

C=CTF4B7E1
c=SECCON_BEGINNERS
D=*.*
d=Disk is not inserted.
E=Error
e=Check the activation code...

F=E3c0Iefcc2yUB5gvPWge1vHQK+TBuUYzST7hT+VrPDhjBt0HCAo5FLohfs/t2Vf5
f=Activated.
G=Information
g=Activation code is invalid.
H=/Activation;component/mainwindow.xaml
h=StatusLabel

次に、AES暗号化しているコードを確認する。

// A.a
// 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();
}

このメソッドへの値の受け渡しを解析した結果を以下に整理する。

  • 暗号化対象のthis.A()は、入力したアクティベーションコードの文字列。
  • IVにセットするthis.b()は、定数Cを2回繰り返した文字列。
  • Keyにセットするthis.B()は、定数cの文字列。
  • メソッドがリターンする文字列を定数Fと突き合わせしている。

よって、定数FをBase64デコードしてAES復号すればよさそうだ。
復号コードは以下のとおり。

using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
public class Program
{
    public static void Main()
    {
        var plainText = string.Empty;
        var cipherText = "E3c0Iefcc2yUB5gvPWge1vHQK+TBuUYzST7hT+VrPDhjBt0HCAo5FLohfs/t2Vf5";
     
        var csp = new AesCryptoServiceProvider();
        csp.BlockSize = 128;
        csp.KeySize = 256;
        csp.Mode = CipherMode.ECB;
        csp.Padding = PaddingMode.PKCS7;
        csp.IV = Encoding.ASCII.GetBytes("CTF4B7E1CTF4B7E1");
        csp.Key = Encoding.ASCII.GetBytes("SECCON_BEGINNERS");
     
        using (var inms = new MemoryStream(Convert.FromBase64String(cipherText)))
        using (var decryptor = csp.CreateDecryptor())
        using (var cs = new CryptoStream(inms, decryptor, CryptoStreamMode.Read))
        using (var reader = new StreamReader(cs))
        {
            plainText = reader.ReadToEnd();
        }
     
        Console.WriteLine(plainText);
    }
}

実行結果は以下の通り。こちらも.NET Fiddle上で実行可能なリンクも載せておく。
https://dotnetfiddle.net/VKMTVa

ae03c6f3f9c13e6ee678a92fc2e2dcc5

フラグゲット。
ctf4b{ae03c6f3f9c13e6ee678a92fc2e2dcc5}