SECCON Beginners CTF 2018 - Activation
問題文
この問題の FLAG は ctf4b{アクティベーションコード} です。
添付ファイル:Activation_492f6d44cb836cf2cd9279ff3f51d5adc1e132d8.zip
writeup
展開するとexeファイル。実行してみる。
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}