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は前回同様解けませんでした。

SECCON Beginners CTF 2020 WriteUpのようなもの

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

SECCON Beginners CTF 2020にチームMIS.Wとしてsoeki、musaprg、shiro、tommi、aoiと参加しました。結果は700点、144位(/1009)でした。

f:id:SUAN:20200524153620p:plain
SCORE

WriteUpとか言っていますが、力業のごり押しなので悪しからず。想定解は私以外の人の方がもっと詳しくわかりやすく書いてあると思います。

SECCON Challenges

解けたもの

[MISC] emoemoencode

emoemoencodeをとりあえず

$ cat emoemoencode.txt

してみました。すると絵文字がたくさん現れてきました。

🍣🍴🍦🌴🍢🍻🍳🍴🍥🍧🍡🍮🌰🍧🍲🍡🍰🍨🍹🍟🍢🍹🍟🍥🍭🌰🌰🌰🌰🌰🌰🍪🍩🍽

栗が多い。ま、当然なんのこっちゃわからないのでバイナリエディタを使ってバイナリを見てみました。

f:id:SUAN:20200524160025p:plain
バイナリ
ここで私は最初、全角英数を変換してこのバイナリにしていると思い、ctf4b{のバイナリと比較してみましたが、変換法則が見つからなかったので断念。
次に半角英数を見てみました。ctf4b{のバイナリと比べてみると、いい感じに規則が見つかりました。

F0 9F 8D XXの場合は
XXA0なら全体を60に置き換え、B0なら全体を70に置き換え、のように行いました。
例: F0 9F 8D A060

F0 9F 8C XXの場合は
XXB0なら全体を30に置き換え。
例: F0 9F 8C B030

これによって得られたフラッグは

ctf4b{stegan0graphy_by_em000000ji}

[REVERSING] mask

とりあえず./maskをして実行すると、

$ ./mask 
Usage: ./mask [FLAG]

と返ってくる。ので今度は[FLAG]に文字を入れて実行。

$ ./mask ctf4b{}
Putting on masks...
atd4`qu
c`b bki
Wrong FLAG. Try again.

と返ってきた。何かしら変換を行って2つの何かと比較しているようだ。

$ strings mask

を実行し、文字列を見てみると怪しい2つの文字列を発見。

atd4`qdedtUpetepqeUdaaeUeaqau
c`b bk`kj`KbababcaKbacaKiacki

あとはこれに合うように[FLAG]を決めれば良いと思い、すべてのアルファベットを探索。 ← これを力業という。

元の文字 変化後A 変化後B
a a a
b ` b
c a c
d d `
e e a
f d b
g e c
h ` h
i a i
j ` j
k a k
l d h
m e i
n d j
o e k
p p `
q q a
r p b
s q c
t t `
u u a
v t b
w u c
x p h
y q i
z p j

ここから存在しないものは記号をテキトーに入れていき、得られたフラッグは

ctf4b{dont_reverse_face_mask}

[REVERSING] yakisoba

とりあえず./yakisobaをして実行すると、

$ ./yakisoba 
FLAG: ctf4b{}
Wrong!

FLAGにはとりあえずテキトーに入れてみた。
今回はmaskとは違い、何も出てこない。strings yakisobaをしても出てこない。
何も出てこないのは困るので逆アセンブルしました。
IDAを使用して逆アセンブルをし、グラフとして出てきたもの見ていたら、1文字ずつ正誤判定しているようでした。
ので、グラフをたどっていって無事フラグが手に入りました。
ctf4b{sp4gh3tt1_r1pp3r1n0}

f:id:SUAN:20200524155122p:plain
IDA
↑これをちまちま一つずつ見ていったという、どう考えても時間かかる力業で解きました。

え?アセンブリを読めって?嫌だよ面倒くさい()

解けなかったもの

[REVERSING] siblangs

siblangs.apkだったのでとりあえず手持ちのXperia XZ Premiumにインストール、実行。

f:id:SUAN:20200524162234p:plain
siblangs1
f:id:SUAN:20200524162308p:plain
siblangs2
f:id:SUAN:20200524162333p:plain
siblangs3
ここでVALIDATE AはiOSで動かせと言われる。apkなので当然動くわけない。(持ってないから動かせないしね)
とりあえず動くVALIDATE Bの方を使うことにした。

.apkファイルはただの.zipなので解凍して、そして中のソースファイル(classes.dex)を.jarに変換後、デコンパイルしてみた。
そこで死ぬほど怪しいものを発見。

package es.o0i.challengeapp.nativemodule;

import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class ValidateFlagModule extends ReactContextBaseJavaModule {
  private static final int GCM_IV_LENGTH = 12;
  
  private final ReactApplicationContext reactContext;
  
  private final SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES");
  
  private final SecureRandom secureRandom = new SecureRandom();
  
  public ValidateFlagModule(ReactApplicationContext paramReactApplicationContext) {
    super(paramReactApplicationContext);
    this.reactContext = paramReactApplicationContext;
  }
  
  public String getName() {
    return "ValidateFlagModule";
  }
  
  @ReactMethod
  public void validate(String paramString, Callback paramCallback) {
    byte[] arrayOfByte = new byte[43];
    arrayOfByte[0] = 95;
    arrayOfByte[1] = -59;
    arrayOfByte[2] = -20;
    arrayOfByte[3] = -93;
    arrayOfByte[4] = -70;
    arrayOfByte[5] = 0;
    arrayOfByte[6] = -32;
    arrayOfByte[7] = -93;
    arrayOfByte[8] = -23;
    arrayOfByte[9] = 63;
    arrayOfByte[10] = -9;
    arrayOfByte[11] = 60;
    arrayOfByte[12] = 86;
    arrayOfByte[13] = 123;
    arrayOfByte[14] = -61;
    arrayOfByte[15] = -8;
    arrayOfByte[16] = 17;
    arrayOfByte[17] = -113;
    arrayOfByte[18] = -106;
    arrayOfByte[19] = 28;
    arrayOfByte[20] = 99;
    arrayOfByte[21] = -72;
    arrayOfByte[22] = -3;
    arrayOfByte[23] = 1;
    arrayOfByte[24] = -41;
    arrayOfByte[25] = -123;
    arrayOfByte[26] = 17;
    arrayOfByte[27] = 93;
    arrayOfByte[28] = -36;
    arrayOfByte[29] = 45;
    arrayOfByte[30] = 18;
    arrayOfByte[31] = 71;
    arrayOfByte[32] = 61;
    arrayOfByte[33] = 70;
    arrayOfByte[34] = -117;
    arrayOfByte[35] = -55;
    arrayOfByte[36] = 107;
    arrayOfByte[37] = -75;
    arrayOfByte[38] = -89;
    arrayOfByte[39] = 3;
    arrayOfByte[40] = 94;
    arrayOfByte[41] = -71;
    arrayOfByte[42] = 30;
    try {
      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
      GCMParameterSpec gCMParameterSpec = new GCMParameterSpec(128, arrayOfByte, 0, 12);
      cipher.init(2, this.secretKey, gCMParameterSpec);
      arrayOfByte = cipher.doFinal(arrayOfByte, 12, arrayOfByte.length - 12);
      byte[] arrayOfByte1 = paramString.getBytes();
      for (int i = 0;; i++) {
        if (i < arrayOfByte.length) {
          if (arrayOfByte1[i + 22] != arrayOfByte[i]) {
            paramCallback.invoke(new Object[] { Boolean.valueOf(false) });
            return;
          } 
        } else {
          paramCallback.invoke(new Object[] { Boolean.valueOf(true) });
          return;
        } 
      } 
    } catch (Exception exception) {
      paramCallback.invoke(new Object[] { Boolean.valueOf(false) });
      return;
    } 
  }
}

どうもエンコードされたByte列をデコードしてから入力文字列の23文字目から比較しているらしい。
ということで自前でJavaでその部分だけ実行してみた。

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

class cyph {

    private static final SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES");

    public static void main(String args[]) throws Exception {
        byte[] arrayOfByte = new byte[43];
        arrayOfByte[0] = 95;
        arrayOfByte[1] = -59;
        arrayOfByte[2] = -20;
        arrayOfByte[3] = -93;
        arrayOfByte[4] = -70;
        arrayOfByte[5] = 0;
        arrayOfByte[6] = -32;
        arrayOfByte[7] = -93;
        arrayOfByte[8] = -23;
        arrayOfByte[9] = 63;
        arrayOfByte[10] = -9;
        arrayOfByte[11] = 60;
        arrayOfByte[12] = 86;
        arrayOfByte[13] = 123;
        arrayOfByte[14] = -61;
        arrayOfByte[15] = -8;
        arrayOfByte[16] = 17;
        arrayOfByte[17] = -113;
        arrayOfByte[18] = -106;
        arrayOfByte[19] = 28;
        arrayOfByte[20] = 99;
        arrayOfByte[21] = -72;
        arrayOfByte[22] = -3;
        arrayOfByte[23] = 1;
        arrayOfByte[24] = -41;
        arrayOfByte[25] = -123;
        arrayOfByte[26] = 17;
        arrayOfByte[27] = 93;
        arrayOfByte[28] = -36;
        arrayOfByte[29] = 45;
        arrayOfByte[30] = 18;
        arrayOfByte[31] = 71;
        arrayOfByte[32] = 61;
        arrayOfByte[33] = 70;
        arrayOfByte[34] = -117;
        arrayOfByte[35] = -55;
        arrayOfByte[36] = 107;
        arrayOfByte[37] = -75;
        arrayOfByte[38] = -89;
        arrayOfByte[39] = 3;
        arrayOfByte[40] = 94;
        arrayOfByte[41] = -71;
        arrayOfByte[42] = 30;
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec gCMParameterSpec = new GCMParameterSpec(128, arrayOfByte, 0, 12);
        cipher.init(2, secretKey, gCMParameterSpec);
        arrayOfByte = cipher.doFinal(arrayOfByte, 12, arrayOfByte.length - 12);
        String text = new String(arrayOfByte);
        System.out.println(text);
    }
}

すると
1pt_3verywhere}
とだけ出てきた。後ろ半分はこれでわかった。

f:id:SUAN:20200524164041p:plain
siblangs4
ただ、前半分はわかりませんでした。他の人のWriteUpを見るとReact Nativeがーーーって書いてあってたしかにそんな文字見たなとなりました。

くやちーーーーー

SECCONを終えて

REVERSING、MISCが解けてちょっと嬉しい。前回はMISCしか解けなかったので…
PWNやりたいって言っていた気がするけどまだ実力不足で何もわかりませんでした(´;ω;`)

SECCONに初めて参加した参加記録 【SECCON 2019】

SECCONに参加しました!

SECCON2019に、チームMIS.Wとしてmusaprg、soeki、shiroと一緒に参加しました。結果は456点で148位でした。

SECCON結果
SECCON
ほぼ初めてな人ばかりが集まるチームで「目標は500点くらい取れればいいよね」と言っていたので、良い方ではないかと思っています。

SECCON Challenges

Beeeeeeeeeer

まずはBeeeeeeeeeerです。 Let's decode! と問題分に書かれています。

echo -e "\033#8"
sleep 1
C=$(tput cols)
L=$(tput lines)
for ID in $(seq $(($L * $C * 6))); do
    x=$(($RANDOM % $C))
    y=$(($RANDOM % $L))
    printf "\033[${y};${x}f "
done
for ID in $(seq $(($L * $C * 6))); do
    x=$(($RANDOM % $C))
    y=$(($RANDOM % $L))
    printf "\033[${y};${x}fF"
done
clear
echo TGV0J3MgZGVjb3JkaW5nISjiiafiiIDiiaYqKQo= | base64 -d
read
$'\164\162\141\160' '' $'\61' $'\62' $'\63' $'\x31\x35' $'\x31\x38' $'\u0031\u0039' $(echo MjAK | base64 -d)
echo $- | grep x && exit
$(echo =btB | rev | tr A-Za-z N-ZA-Mn-za-m | base64 -d) | $(echo =bNpyW3M | rev | tr A-Za-z N-ZA-Mn-za-m | base64 -d) $(echo XRKY | rev | tr A-Za-z N-ZA-Mn-za-m | base64 -d) $(echo =ogO | rev | base64 -d) && exit
if [ -z "$1" ]; then ID=$'\x6e\x61\x6e\x64\x6f\x6b\x75'; else ID="$1"; fi
if whoami | grep -e root -e user -e adm -e nobody -e test -e "$ID" >/dev/null; then :; else exit; fi
for i in $($'\x73\x65\x71' $((RANDOM % 10))); do $($'\x65\x63\x68\x6f' c2xlZXAK | $'\x62\x61\x73\x65\x36\x34' -d) $((RANDOM % 300)); done
$'\145\143\150\157' $- | $(echo =oAclJ3Z | rev | base64 -d) $(echo =oAe | rev | base64 -d) && $($'\x65\x63\x68\x6f' ZXhpdAo= | $'\x62\x61\x73\x65\x36\x34' $'\x2d\x64')
$(echo =btB | rev | tr A-Za-z N-ZA-Mn-za-m | base64 -d) | $(echo M3WypNb= | tr A-Za-z N-ZA-Mn-za-m | base64 -d) $(echo YKRX | tr A-Za-z N-ZA-Mn-za-m | base64 -d) $(echo Btb= | tr A-Za-z N-ZA-Mn-za-m | base64 -d) && exit
$'\145\170\160\157\162\164' $'\u0053\u0031'=$(echo aG9nZWZ1Z2EK | base64 -d)

Beeeeeeeeeerを見る限り、Shell Scriptであることがわかるので、実行権限を与えて実行してみると、

Let's decording!(≧∀≦*)

というメッセージが。
また、 read 以降の文が全てエンコードされていることがわかります。なのでとりあえずdecodeします。

read
trap '' 1 2 3 15 18 19 20
echo $- | grep x && exit
: | grep -q : && exit
if [ -z "$1" ]; then ID=nandoku; else ID="$1"; fi
if whoami | grep -e root -e user -e adm -e nobody -e test -e "$ID" >/dev/null; then :; else exit; fi
for i in $(seq $((RANDOM % 10))); do sleep $((RANDOM % 300)); done
echo $- | grep x && exit
: | grep -q : && exit
export S1=hogefuga

いらないコードばっかりですね。必要なのは export S1=hogefuga だけです。 なんで執拗にexitさせてくるんだ
更にその後のコードもデコードしてみると、

: killall sh
: shutdown
$(: pkill sh)
: shutdown
: shutdown
: poweroff
: poweroff
: exit
: poweroff
: shutdown
: shutdown
: shutdown
: pkill sh
: shutdown

こうなっていました。これ以上にこの並びが続いていて、どんだけ電源落としたいんだってなります…
重要なのは export S1=hogefuga とその後のechoの1行だけです。
その場所だけ隔離してbashで実行してみたところ、ビープ音のなった回数をランダムで聞いてくるとても面倒くさいプログラムにぶち当たります。(間違えるとexitされるのでたちが悪い)
| base64 -d | gunzip | bash

| base64 -d | gunzip > Beeeeeeeeeer2.sh
として出力してみると

for k in $(seq $((RANDOM % 10 + 1))); do
    l=$((RANDOM % 10 + 1))
    for m in $($(echo ==gCxV2c | rev | base64 -d) $l); do
        echo -ne '\a'
       sleep 1
    done
    echo "How many beeps?"
    read n </dev/tty
    export n
    if [ "$n" -ne "$l" ]; then exit; fi
done
echo -ne '\a'
sleep 1
echo -ne '\a'
sleep 1
echo -ne '\a'
sleep 1
echo "How many beeps?"
read n </dev/tty
export n

何だこれは!!!私の邪魔をしてくるコードがたくさん
export n だけが必要であり、n=3であることがわかるので、 export 3 だけを残して削除します。 更にその後にもechoがあり、そこがまた重要なのでそこだけを隔離して実行してみます。 Enter the password と出てきますが、パスワードなんて知りません。ので、やっぱり
| base64 -d | openssl aes-256-cbc -d -pass pass:$(echo -n $n | md5sum | cut -c2,3,5,12) -md md5 2>/dev/null | bash

| base64 -d | openssl aes-256-cbc -d -pass pass:cccc -md md5 2>/dev/null > Beeeeeeeeeer3.sh
として見てみます。

__=$(. 2>&1);__=${__##*.};__=$(. 2>&1);__=${__##*.};${__:$(($[($[$$/$$]<<$[$$/$$]<<$[$$/$$]<<$[$$/$$])+$[$$/$$]]+$[($[$$/$$]<<$[$$/$$]<<$[$$/$$]<<$[$$/$$])+$[$$/$$]]+$[$$/$$])):$((___=___^___||++___))}${__:$[$[$$/$$]<<$[$$/$$]<<$[$$/$$]]:$((___=___^___||++___))}${__:$(($[($[$$/$$]<<$[$$/$$]<<$[$$/$$]<<$[$$/$$])+$[$$/$$]]+$[($[$$/$$]<<$[$$/$$]<<$[$$/$$]<<$[$$/$$])+$[$$/$$]])):$((___=___^___||++___))} -- {z..A};${@:$((____=(____^____||++____)+

のような感じで、何もわかりません。が、一番最後に echo SECCON{$S1$n$_____} という文字列を発見。
S1 = hogefuga
n = 3
であるので、最後の____だけわかればいいことになります。
またこれをデコードすると

__=$(. 2>&1)
__=${__##*.}
__=$(. 2>&1)
__=${__##*.}
set -- {z..A}
echo Enter the password
read _____
: password is bash
read _____
echo -e '\033[?7h'

となりました。ちょこっと内容が消え去っていて私はここで戸惑いました。(←ちゃんとデコードしろ)
: password is bash 、神かこれ。ということで Enter the passwordbash と入れると Good Job! と帰ってくるので正解だとわかります。
これらからflagは
SECCON{hogefuga3bash}
となります。

coffee_break

何がコーヒーだ!コーヒー要素なんてないじゃないか!と思われた方、正解です。
とりあえず配布されたpythonコードを見てみる。

import sys
from Crypto.Cipher import AES
import base64


def encrypt(key, text):
    s = ''
    for i in range(len(text)):
        s += chr((((ord(text[i]) - 0x20) + (ord(key[i % len(key)]) - 0x20)) % (0x7e - 0x20 + 1)) + 0x20)
    return s


key1 = "SECCON"
key2 = "seccon2019"
text = sys.argv[1]

enc1 = encrypt(key1, text)
cipher = AES.new(key2 + chr(0x00) * (16 - (len(key2) % 16)), AES.MODE_ECB)
p = 16 - (len(enc1) % 16)
enc2 = cipher.encrypt(enc1 + chr(p) * p)
print(base64.b64encode(enc2).decode('ascii'))

cipherとbase 64を使用したところはすぐにデコードできそうですね。なので、

key1 = "SECCON"
key2 = "seccon2019"
text = sys.argv[1]

cipher = AES.new(key2 + chr(0x00) * (16 - (len(key2) % 16)), AES.MODE_ECB)
dec1 = cipher.decrypt(base64.b64decode(text))

これだけでデコードできます。問題は def encrypt(key, text):エンコードされた文字列。
でもこれ、ただの数値計算なので逆算してあげる。すると

def decrypt(key, text):
    s = ''
    for i in range(len(text)):
        s += chr(ord(text[i]) + 0x20 - (ord(key[i % len(key)]) - 0x20) + 0x3F)
    return s

はい。これでOK。 def encrypt が95で割ったあまりを出力しているので正確にはデコードできないが、推測ができれば問題ないのでOK。
またこれはアルファベットしかないとして仮定した場合である。すると
FyRyZNBO2MG6ncd3hEkC/yeYKUseI/CxYoZiIeV2fe/Jmtwx+WbWmU1gtMX9m905

S�����{�uccess_�ecryption_�eah_�eah_S�����}
になった。これから推測するとflagは
SECCON{Success_Decryption_Yeah_Yeah_SECCON}
となる。

pngbomb

画像データがgzで圧縮されているので解凍して表示しようとすると、IHDRが壊れていることがわかる。これしかわからなかったのでダメです。12時間を溶かした問題。

SECCONを終えて

初めて出場したCTFの大会でしたが、とても楽しく、寝る時間を惜しんでいました。
miscしか解いていないけれど、それが一番楽しかった気がする。
Pwn、メモリリークできないです勉強してきます…。