suanの備忘録

SUANが色々行ったこと等を書いていきます。

SECCON Beginners CTF 2021 WriteUpのようなもの

SECCON Beginners CTF 2021に参加しました!

SECCON Beginners CTF 2021にチームM15.(W|X)として参加し、3327点で23位(/943)でした! チームメイトはsoeki, siro53, eren53でした。

f:id:SUAN:20210523141433p:plain
seccon 2021
今回は前回と比べ結構解けたかなという感じですね

嬉しい

SECCON Challenges

解けたもの

[reversing] only_read

名前の通り、challを読めばいい(機械語で)
ま、読むわけないんですけどね機械語でなんて。
ということでghidraにかけます

途中省略しています。

  if (((((((char)local_28 == 'c') && (local_28._1_1_ == 't')) && (local_28._2_1_ == 'f')) &&
       (((local_28._3_1_ == '4' && (local_28._4_1_ == 'b')) &&
        ((local_28._5_1_ == '{' && ((local_28._6_1_ == 'c' && (local_28._7_1_ == '0')))))))) &&
      (((char)local_20 == 'n' &&
       ((((((local_20._1_1_ == '5' && (local_20._2_1_ == 't')) && (local_20._3_1_ == '4')) &&
          ((local_20._4_1_ == 'n' && (local_20._5_1_ == 't')))) &&
         ((local_20._6_1_ == '_' && ((local_20._7_1_ == 'f' && ((char)local_18 == '0')))))) &&
        (local_18._1_1_ == 'l')))))) &&
     ((((local_18._2_1_ == 'd' && (local_18._3_1_ == '1')) && ((char)local_14 == 'n')) &&
      ((local_14._1_1_ == 'g' && (local_12 == '}')))))) {
    puts("Correct");
  }


全部書いてありますね!
ctf4b{c0n5t4nt_f0ld1ng}

[reversing] children

10個の子プロセスを生成し、そのPIDを入力する問題。
ps auxコマンドでプロセスを確認してPIDを入れるだけの簡単なお仕事。
最後にプロセスをいくつ生成した?って聞かれるけどそれもps auxで出てきたのを数えれば終わり。
ctf4b{p0werfu1_tr4sing_t0015_15_usefu1}

[reversing] please_not_trace_me

これはプログラムはちゃんとflagを生成しているのにもかかわらず出力しないタイプのやつ。
でも問題名の通りトレースするとトレースすんなやゴラって怒られます。(prease not trace me...っていう文言でやさしかったわ)
ということでいつものghidra

  local_30 = 9;
  _global_argv = param_2;
  _global_argc = param_1;
  _global_envp = param_3;
  do {
    switch(local_30) {
    case 0:
      local_50 = 2;
      local_30 = 0xb;
      break;
    case 2:
      local_30 = _1_main_flag_func_0(local_38,0xffffffffffffffff,0x10,5);
      break;
    case 5:
      if (local_50 == 6) {
        local_30 = 0x12;
      }
      else {
        local_30 = 8;
      }
      break;
    case 6:
      if (local_40 == 0) {
        local_30 = 0;
      }
      else {
        local_30 = 0xb;
      }
      break;
    case 8:
      fwrite("prease not trace me...\n",1,0x17,stderr);
                    /* WARNING: Subroutine does not return */
      exit(1);
    case 9:
      local_54 = 0;
      local_30 = 10;
      break;
    case 10:
      switch(local_54) {
      case 0:
        local_30 = 0x16;
        break;
      case 1:
        local_30 = 0xf;
        break;
      case 2:
        local_30 = 0x13;
        break;
      case 3:
        local_30 = 0x14;
        break;
      case 4:
        local_30 = 0x11;
        break;
      case 5:
        local_30 = 0x15;
        break;
      default:
        local_30 = 0x12;
      }
      break;
    case 0xb:
      local_38 = ptrace(PTRACE_TRACEME,0,1,0);
      local_30 = 2;
      break;
    case 0xf:
      local_48 = malloc(0x10);
      local_30 = 0x12;
      break;
    case 0x10:
      local_50 = local_50 * 3;
      local_30 = 5;
      break;
    case 0x11:
      puts("flag decrypted. bye.");
      local_30 = 0x12;
      break;
    case 0x12:
      local_54 = local_54 + 1;
      local_30 = 10;
      break;
    case 0x13:
      generate_key(local_48);
      local_30 = 0x12;
      break;
    case 0x14:
      rc4(e,local_48);
      local_30 = 0x12;
      break;
    case 0x15:
                    /* WARNING: Subroutine does not return */
      exit(0);
    case 0x16:
      local_50 = 0;
      local_40 = ptrace(PTRACE_TRACEME,0,1,0);
      local_30 = 6;
    }
  } while( true );

これを見るとlocal_30というものでswitch文を回していることが分かる。
なので、local_30の値を弄ってptraceを通らないようにしてあげればいい。
具体的には0x0F→0x13→0x14って通ればよい。
で、弄ってからトレースする
ctf4b{d1d_y0u_d3crypt_rc4?}

[reversing] firmware

何かのファームウェアのバイナリが落ちてくる。(firmware.bin)
で、これは複数のファイルを一つにまとめたものであることが分かる。
なので、全部分割しちゃえ!!!ってことで分割をするのです。
firmware.binの1行目途中からファイル名とそのMD5ハッシュが記録されているのでそれを頼りに分割する。
んで、こうなる

f:id:SUAN:20210523143237p:plain
firmware 2021
これを見るとfirm以外重要そうなものが見つからないのでfirm(32bit ELF ARM)をghidraで見てみる(いつもの)

          recv(local_11d8,abStack4116,0x1000,0);
          printf("%s",abStack4116);
          memcpy(auStack4520,&DAT_00010ea4,0xf4);
          __n = strlen((char *)abStack4116);
          if (__n != 0x3d) {
            local_10b4 = 0x6f636e49;
            uStack4272 = 0x63657272;
            uStack4268 = 0x61702074;
            uStack4264 = 0x6f777373;
            local_10a4 = 0xa2e6472;
            local_10a0 = 0;
            __n = strlen((char *)&local_10b4);
            send(local_11d8,&local_10b4,__n,0);
            close(local_11d8);
          }
          local_11e0 = 0;
          while (local_11e0 < 0x3d) {
            if ((uint)(abStack4116[local_11e0] ^ 0x53) != auStack4520[local_11e0]) {
              local_10b4 = 0x6f636e49;
              uStack4272 = 0x63657272;
              uStack4268 = 0x61702074;
              uStack4264 = 0x6f777373;
              local_10a4 = 0xa2e6472;
              local_10a0 = 0;
              __n = strlen((char *)&local_10b4);
              send(local_11d8,&local_10b4,__n,0);
              close(local_11d8);
            }
            local_11e0 = local_11e0 + 1;
          }
          local_10b4 = 0x72726f43;
          uStack4272 = 0x20746365;
          uStack4268 = 0x73736170;
          uStack4264 = 0x64726f77;
          local_10a4 = 0xa212121;
          local_10a0 = 0;
          __n = strlen((char *)&local_10b4);
          send(local_11d8,&local_10b4,__n,0);
          close(local_11d8);

すると、文字数(0x3d)と文字列を判別していることが分かる。
文字列については0x53とxor取ってから判別しているので、プログラムに埋め込まれているものに0x53でxorして出力。

#include <stdio.h>

int main(int argc, char *argv[])
{
    char tmp[] = {0x30, 0x27, 0x35, 0x67, 0x31, 0x28, 0x3a, 0x63, 0x27, 0x0c, 0x37, 0x36, 0x25, 0x62, 0x30, 0x36, 0x0c, 0x35, 0x3a, 0x21, 0x3e, 0x24, 0x67, 0x21, 0x36, 0x0c, 0x32, 0x3d, 0x32, 0x62, 0x2a, 0x20, 0x3a, 0x60, 0x0c, 0x21, 0x36, 0x25, 0x60, 0x32, 0x62, 0x20, 0x0c, 0x32, 0x0c, 0x3f, 0x63, 0x27, 0x0c, 0x3c, 0x35, 0x0c, 0x66, 0x36, 0x30, 0x21, 0x36, 0x64, 0x20, 0x2e, 0x59};
    int s = 61;
    for(int i = 0; i < s; i++)
    {
        printf("%c", (tmp[i] ^ 0x53));
    }
    printf("\n");
}

で、終わり。
ctf4b{i0t_dev1ce_firmw4re_ana1ysi3_rev3a1s_a_l0t_of_5ecre7s}

[misc] Mail_Address_Validator

rubyのプログラムを拾えるので見てみると、正規表現を使ってチェックしてるだけ。
チェックがタイムアウト(5秒)するとflagが落ちてくるらしい。
正規表現タイムアウトさせるためにクソ長メールアドレスっぽい文字列を入れた。(約80万文字)
するとあっさり表示された。
ctf4b{1t_15_n0t_0nly_th3_W3b_th4t_15_4ff3ct3d_by_ReDoS}

[misc] fly

Wolf RPGエディタのゲーム内のオブジェクトにflagが隠されているのだけが分かる。
でも壁があって通り抜けられないし煽ってくるキャラクターもいる。

f:id:SUAN:20210523144203p:plain
wolf 1 2021
Wolf RPGエディタは起動時引数にTest_Of_Mainを指定するとデバッグモードで起動することができるので、これを使わない手はない。
デバッグモード時では、F11キーを押すことで固定データのみ更新することができる(可変データはそのまま残る)。
ということで、Wolf RPGエディタをダウンロードした時にあったサンプルのデータを利用して起動する。
この時、Data.wolfData.wolf1などと改名しておき、Game.exeから見えないようにしておく。
f:id:SUAN:20210523144514p:plain
wolf 2 2021
その後、Game.exe Test_Of_Mainコマンドを利用して起動する。
するとサンプルデータのゲームが起動するので、スタートする。
f:id:SUAN:20210523144700p:plain
wolf 3 2021
ここでさっき改名しておいたData.wolf1Data.wolfに戻してF11キーを押すと楽しいことが起きる。
動けない場合は動けるような場所に最初から移動しておいてください。
f:id:SUAN:20210523144842p:plain
wolf 4 2021
f:id:SUAN:20210523144911p:plain
wolf 5 2021
ちなみに青い階段にはイベントが設定されていないのでflagを見つけることができない。
なので、サンプルデータのゲームで場面転換しておくといい。
サンプルデータのゲームで上へ行き、上のマップへ移動してください。
f:id:SUAN:20210523145119p:plain
wolf 6 2021
ここからはさっきと同じで、改名しておいたData.wolf1Data.wolfに戻してF11キーを押す。
すると…?
f:id:SUAN:20210523145221p:plain
wolf 7 2021
ctf4b{b3_c4r3ful_0f_fl135_wh3n_73l3p0r71n6}

[misc] depixelization

pythonのプログラムとflagの画像が落とせる。
ここで普通はプログラムで行っている処理の逆を行いflagを復元しよう!となるだろう。
私はこのプログラムには好きな文字列に同様の処理をすることができるのに気付いたので、全アルファベットと数字と{}_を処理した画像を出力した。

f:id:SUAN:20210523145443p:plain
depixel 1 2021
あとはこれのどれと完全一致するか、というのを判別するプログラムを書き、終わり。

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;

public class Main {
    public static void main(String[] args) {
        try {
            String flagFile = "ファイル名書き換えて";
            String alphabetFile = "ファイル名書き換えて";

            BufferedImage flagImg = ImageIO.read(new File(flagFile));
            BufferedImage alphabetImg = ImageIO.read(new File(alphabetFile));

            int flagWidth = flagImg.getWidth();
            int alphabetWidth = alphabetImg.getWidth();

            for (int i = 0; i < flagWidth; i += 85) {
                boolean match = false;
                int matchnum = 0;
                for (int j = 0; j < alphabetWidth && !match; j += 85) {
                    boolean ok = true;
                    for (int x = 5; x < 85 && ok; x += 10) {
                        for (int y = 5; y < 100 && ok; y += 10) {
                            if (flagImg.getRGB(i + x, y) != alphabetImg.getRGB(j + x, y)) {
                                ok = false;
                            }
                        }
                    }
                    if (ok) {
                        match = true;
                        matchnum = j / 85;
                    }
                }
                if (match) {
                    if (matchnum < 'z' - 'a') {
                        System.out.print((char) (matchnum + 'a'));
                    } else {
                        matchnum = matchnum - 'z' + 'a' - 1;
                        if (matchnum < 10) {
                            System.out.print(matchnum);
                        } else {
                            char a = ' ';
                            switch(matchnum)
                            {
                                case 10:
                                    a = '{';
                                    break;
                                case 11:
                                    a = '}';
                                    break;
                                case 12:
                                    a = '_';
                                    break;
                            }
                            System.out.print(a);
                        }
                    }
                } else {
                    System.out.print(".");
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

これだとnとmの判別がつかないらしく、実際nがmとして認識された。
ctf4b{1f_y0u_p1x_y0u_c4m_d3p1x} と出力されたが、間違いだったのでmをnに修正。
ctf4b{1f_y0u_p1x_y0u_c4n_d3p1x}

SECCONの感想

今回は前回と比べ倍以上の問題が解けたので大満足
PWNは前回同様解けませんでした。