TSALVIA技術メモ

CTFのWriteupや気になったツールについて書いていきます。また、このサイトはGoogle Analyticsを利用しています。

nullcon HackIM 2020 Writeup

nullcon HackIM 2020 について

nullcon HackIM 2020 が開催されました。
2020年02月08日午前2時から2020年02月09日の午後2時(36時間)

ctf.nullcon.net

今回は出かける用事があり、他の問題をほとんど見れていませんが、解いた問題の難易度は低めでした。 今回は、チームで参加しました。 結果は、130/1208位で、426点でした。実際に5問解くことができたので、そのWriteupを紹介します。

f:id:tsalvia:20200209163912p:plain

nullcon HackIM 2020 Writeup(5問)

ez pz sanity check(misc)

問題

get this plz

hackim20{no_insanity}

解答例

チュートリアル問題で、問題文にフラグが書かれていました。

FLAG

hackim20{no_insanity}

Zelda and the Zombies(Zelda Adventures)

問題

Welcome to Zelda adventures.

You wake up in Zelda land. Realizing you are the lone survivor to a deadly virus and you are surrounded by zombies. Zelda remembers the words of his master. He said, "It would take years for any mortal man to kill those zombies".

tl;dr: Kill any 1 NPC to get the flag.

This challenge does not follow the flag format

Download Game Here: https://drive.google.com/file/d/1W_KJhSn6wTQiUYBNY5xhmbseJXC0l2Up Author: @lionaneesh

解答例

問題文のリンクからファイルをダウンロードすると、Unity製のゲームが入っていました。 実行すると、以下のような感じになっていました。

f:id:tsalvia:20200208230505p:plain

問題文には、「Kill any 1 NPC to get the flag.」と書かれているので、NPCを倒すことを目標にします。 Unity製のソフトは、Managed\Assembly-CSharp.dllをデコンパイルすれば読むことができます。 dnSpyでデコンパイルして、ダメージ計算処理の部分を読むと、以下のようになっていました。

f:id:tsalvia:20200208104918p:plain

以下のように変更し、ダメージを受けると体力が0になるように調整しました。

f:id:tsalvia:20200208104824p:plain

実行してみると、NPCがプレイヤーに触れた瞬間消えて、フラグが表示されました。

f:id:tsalvia:20200208104524p:plain

フラグが表示されましたが、結構読みづらいので、以下のツールを使って、アセットを抽出させました。

github.com

アセット内を探索していると、以下のようなフォントが見つかりました。

f:id:tsalvia:20200208104541p:plain

このフォントとフラグを見比べると、「REVOLUTIONSTARTSWITHME」となっていました。

FLAG

REVOLUTIONSTARTSWITHME

Zelda at the Swamp(Zelda Adventures)

問題

Zelda realized that he could not only fight on land but on waters as well.

Head to FlagTown and cross the pond to get to the flag!

This challenge does not follow the flag format

Download Game Here: https://drive.google.com/file/d/1W_KJhSn6wTQiUYBNY5xhmbseJXC0l2Up Author: @lionaneesh

解答例

今回の問題も前回の「Zelda and the Zombies」で使われていたUnity製のゲームを使うようです。 今回は、「FlagTown」に行って池を渡ってフラグを取ることが目標だそうです。

いろいろ歩き回ってみると、「FlagTown」というところを見つけました。池の上にフラグが浮いています。 ただし、池には当たり判定があり、池の中に入ることができません。

f:id:tsalvia:20200208230004p:plain

Unitiyの当たり判定について調べていると、以下のページで気になる文章を見つけました。

docs.unity3d.com

このページには、以下のように書かれています。

Collision Detection を Discrete (不連続) に設定すると、Rigidbody 2D と Collider 2D を持つゲームオブジェクトの動く速度が十分速い場合は、物理計算の更新の間に、ゲームオブジェクトが、互いに重なったり通り抜けたりすることができます。衝突の接触は新しい位置でのみ発生します。

動く速度が速いとすり抜けてしまう場合があるそうです。 Managed\Assembly-CSharp.dll を dnSpyでデコンパイルして、調査していると、以下の処理が見つかりました。 この処理を調整すれば、速度を上げられそうです。

f:id:tsalvia:20200208230032p:plain

今回は、5倍速になるように調整しました。

f:id:tsalvia:20200208225821p:plain

実行して、池に向かって走ると、オブジェクトをすり抜けることができました。 すり抜けると、別の場所にワープし、地面にフラグが書かれていました。

f:id:tsalvia:20200208225716p:plain

FLAG

BENDTHERULES42PIRATE

Zelda crossing the land's end(Zelda Adventures)

問題

Zelda is on the quest to find other survivors and is now standing at the land's end trying to cross over.

This challenge does not follow the flag format

Download Game Here: https://drive.google.com/file/d/1W_KJhSn6wTQiUYBNY5xhmbseJXC0l2Up Author: @lionaneesh

解答例

今回の問題も前回の「Zelda and the Zombies」、「Zelda at the Swamp」で使われていたUnity製のゲームを使うようです。 今回は、他の生存者を探すことが目標のようです。

前回の「Zelda at the Swamp」と同様に移動速度を上げて、壁抜けしながら歩き回っていると、右上の方に小さな集落を見つけました。

f:id:tsalvia:20200208231818p:plain

地面に書かれていた文字がフラグとなっていました。

FLAG

EXPLORERFORLIFE

Zelda and the Space Puzzle(Zelda Adventures)

問題

This challenge uses a different binary than other 3. And it follows the flag format.

After conquering on land. Zelda is stuck in another dimension in Space. He will be stuck here till he solves the puzzle that the dark lord has tested him with.

2 platforms are connected with multiple paths between them. There are 6 weird buttons in Space, walking over which makes up a key of sorts. Help Zelda solve the Space Puzzle.

Download Game Here: https://drive.google.com/file/d/1kP4yp9jxJVOa0gP7DeHPm_bLCF2X4Au_/view?usp=sharing Author: @lionaneesh

解答例

今回もUnity製のゲームのようですが、他の3つとは違うものとなっていました。 実行してみると、以下の場所からスタートします。

f:id:tsalvia:20200209162259p:plain

壁抜けしながら、色々探索してみましたが、よく分かりませんでした。 しばらく、Managed\Assembly-CSharp.dll を読んでいると、以下の処理が見つかりました。

   private void OnGUI()
    {
        StringBuilder stringBuilder = new StringBuilder();
        string text = stringBuilder.ToString();
        if (this.checkpoints.Count >= 1)
        {
            foreach (string value in this.checkpoints)
            {
                stringBuilder.Append(value);
            }
            text = stringBuilder.ToString();
            if (string.Equals(this.checkpoints[this.checkpoints.Count - 1], "final"))
            {
                if (string.Equals(this.encrypted, string.Empty))
                {
                    try
                    {
                        this.encrypted = this.Decrypt("pI0gDg911A3Qcf++L3rvfkwIEkXsg4jq6pwOHMgG1VlpPuE9t4eljr4fQvXUa9bMJN4TL+DzQoj8aHTe1sNt+y5FND+gqn04OOltMhv/sms=", text);
                    }
                    catch (CryptographicException ex)
                    {
                        this.encrypted = "wrong key Zelda! :(";
                    }
                    Debug.Log(this.encrypted);
                }
            }
            else
            {
                this.encrypted = string.Empty;
            }
        }
        if (!string.Equals(text, string.Empty))
        {
            GUI.Label(new Rect(0f, 5f, 400f, 105f), text, this.style);
        }
        if (!string.Equals(this.encrypted, string.Empty))
        {
            GUI.Label(new Rect(0f, 100f, 400f, 100f), this.encrypted, this.style);
        }
    }

何かを復号している処理が確認できます。AESの暗号になっており、復号にはキーを特定する必要がありそうです。 上記の処理をよく読むと、checkpointsの文字列を結合したものがキーとなっていそうです。 また、checkpointsの初期値は、0、最後の値は、"final"となっているようです。

   private void Start()
    {
        this.checkpoints = new List<string>();
        this.style.normal.textColor = Color.green;
        this.checkpoints.Add("0");
        this.encrypted = string.Empty;
    }

checkpointsの追記している処理を探してみると、以下の処理が見つかりました。 peppersaltchillypicklesoreganomasalaを組み合わせたものが、キーとなっていそうです。

   private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            PlayerAttrs component = other.GetComponent<PlayerAttrs>();
            Debug.Log(this.placeText.text);
            if (string.Equals(this.placeText.text, "CheckPoint 1!"))
            {
                string a = component.checkpoints[component.checkpoints.Count - 1];
                if (!string.Equals(a, "pepper"))
                {
                    component.checkpoints.Add("pepper");
                }
                Debug.Log(component.checkpoints.Count);
            }
            else if (string.Equals(this.placeText.text, "CheckPoint 2!"))
            {
                string a2 = component.checkpoints[component.checkpoints.Count - 1];
                if (!string.Equals(a2, "salt"))
                {
                    component.checkpoints.Add("salt");
                }
                Debug.Log(component.checkpoints.Count);
            }
            else if (string.Equals(this.placeText.text, "CheckPoint 3!"))
            {
                string a3 = component.checkpoints[component.checkpoints.Count - 1];
                if (!string.Equals(a3, "chilly"))
                {
                    component.checkpoints.Add("chilly");
                }
                Debug.Log(component.checkpoints.Count);
            }
            else if (string.Equals(this.placeText.text, "CheckPoint 4!"))
            {
                string a4 = component.checkpoints[component.checkpoints.Count - 1];
                if (!string.Equals(a4, "pickles"))
                {
                    component.checkpoints.Add("pickles");
                }
                Debug.Log(component.checkpoints.Count);
            }
            else if (string.Equals(this.placeText.text, "CheckPoint 5!"))
            {
                string a5 = component.checkpoints[component.checkpoints.Count - 1];
                if (!string.Equals(a5, "oregano"))
                {
                    component.checkpoints.Add("oregano");
                }
                Debug.Log(component.checkpoints.Count);
            }
            else if (string.Equals(this.placeText.text, "CheckPoint 6!"))
            {
                string a6 = component.checkpoints[component.checkpoints.Count - 1];
                if (!string.Equals(a6, "masala"))
                {
                    component.checkpoints.Add("masala");
                }
                Debug.Log(component.checkpoints.Count);
            }
            else if (string.Equals(this.placeText.text, "Final"))
            {
                Debug.Log("Hey welcome to final!\n");
                string a7 = component.checkpoints[component.checkpoints.Count - 1];
                if (!string.Equals(a7, "final"))
                {
                    component.checkpoints.Add("final");
                }
                Debug.Log(component.checkpoints.Count);
            }
        }
    }

キーの組み合わせをすべて出力するスクリプトと復号するプログラムを書きました。 復号処理は、Managed\Assembly-CSharp.dllのものをそのまま使用しています。

import itertools

def main():
    keys = (
        'pepper',
        'salt',
        'chilly',
        'pickles',
        'oregano',
        'masala',
    )

    key_pattern_list = list(itertools.permutations(keys, len(keys)))

    for key in key_pattern_list:
        print('{}{}{}'.format(0, ''.join(key), 'final'))

if __name__ == '__main__':
    main()
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace csharp
{
    class Program
    {
        static string Decrypt(string cipherText, string key)
        {
            byte[] array = Convert.FromBase64String(cipherText);
            using (Aes aes = Aes.Create())
            {
                Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(key, new byte[]
                {
                    73,
                    118,
                    97,
                    110,
                    32,
                    77,
                    101,
                    100,
                    118,
                    101,
                    100,
                    101,
                    118
                });
                aes.Key = rfc2898DeriveBytes.GetBytes(32);
                aes.IV = rfc2898DeriveBytes.GetBytes(16);
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Write))
                    {
                        cryptoStream.Write(array, 0, array.Length);
                        cryptoStream.Close();
                    }
                    cipherText = Encoding.Unicode.GetString(memoryStream.ToArray());
                }
            }
            return cipherText;
        }
        static void Main(string[] args)
        {
            string text = "";
            StreamReader file = new StreamReader(args[0]);

            while ((text = file.ReadLine()) != null)
            {
                string decrypted;
                try
                {
                    decrypted = Decrypt("pI0gDg911A3Qcf++L3rvfkwIEkXsg4jq6pwOHMgG1VlpPuE9t4eljr4fQvXUa9bMJN4TL+DzQoj8aHTe1sNt+y5FND+gqn04OOltMhv/sms=", text);
                }
                catch (CryptographicException)
                {
                    continue;
                }
                Console.WriteLine("{0}: {1}", text, decrypted);
            }
        }
    }
}

実行すると、フラグを取得することができました。

$ python create_keys.py > key_list
$ dotnet.exe run key_list | grep -v "?"
0peppermasalapicklessaltoreganochillyfinal: hackim20{z3lda_s0lved_the_sp4ce_puzzl3}

FLAG

hackim20{z3lda_s0lved_the_sp4ce_puzzl3}