net-nyan-cat

俺たちがインフラ野郎Aチーム

BITS CTF 2019 - writeup

BITS CTF 2019のwriteupです。
結果がクソザコだったので、今年は無限にがんばろうと思います。

Web - sanity check

ページにアクセスすると謎の文字列。 ノリでbase64でデコードするとフラグ。

$ echo -n "QklUU0NURnt3M2xjMG0zXzcwXzcwcF8xX3AzcmMzbjd9" | base64 -d
BITSCTF{w3lc0m3_70_70p_1_p3rc3n7}

Rev - Me= Ur_frnd

.pycが与えられる。
.pyc形式はpythonのコードをバイトコードコンパイルしたものの拡張子だが、 簡単に.py形式に逆コンパイルできる。
今回はuncompyle2を用いて、逆コンパイルした。

github.com

コンパイルしたコードは以下。

# 2019.02.03 17:31:31 JST
#Embedded file name: ctf.py
import md5
md5s = [138940039282915521857245566551508789980L,
 301199927827248443324098746649658754844L,
 115532968768857561191818061218773701813L,
 191607221511277924288674550230099507123L,
 262751473846662512998464656050881387235L,
 75522546465545609216785689852028789081L,
 306267194955346451948103584154404505794L,
 81713620093652557693299093431127237017L,
 130346843513741821936326611370813041710L,
 267600260873798988999886324804713174180L,
 313232226741092686456213541074397062500L]
print 'Tell me something you know ...'
flag = raw_input("I'd prefer the flag: ")
if 'CTF' in flag:
    if len(flag) > 69:
        print "That's not the flag."
        exit()
    if len(flag) % 4 != 0:
        print "That's not the flag."
        exit()
    for i in range(0, len(flag), 4):
        s = flag[i:i + 4]
        if int('0x' + md5.new(s).hexdigest(), 16) != md5s[i / 4]:
            print "That's not the flag."
            exit()

    if md5.new(str(len(flag))).hexdigest() == 'f7177163c833dff4b38fc8d2872f1ec6':
        print 'Nice. Now submit the flag and get those points.'
    else:
        print "That's not the flag."
else:
    print 'Thanks for sharing. <3'
note = 'Comment this and any line(s), if exist, following this note.'
#+++ okay decompyling share_plz.pyc
# decompiled 1 files: 1 okay, 0 failed, 0 verify failed
# 2019.02.03 17:31:31 JST

ポイントは以下。

  • 入力値(変数flag)に文字列"CTF"が含まれていること
  • 入力値は69文字未満であること
  • 入力値は4の整数倍の長さであること

それらをif文でチェックし、forループの中で以下の動作をしている。

  • 入力値を4文字ずつに切り出し、変数sに保管
  • 変数sの値をMD5でハッシュ化(16進数)し、それをint(10進数)にしている。
  • 配列md5sの要素にに突き合わせて同一の値か確認している。

つまり、本来のflagの文字列の長さは4 * len(md5s)であることが予想できる。
また、入力値から4文字ずつMD5の値を検証するということで、配列md5sの要素に対し、 ブルートフォースによる突き合わせで文字列を抜き出すアプローチをとった。
作成したコードは以下。

#!/bin/python3

import string
import itertools
from hashlib import md5

md5s = [138940039282915521857245566551508789980,
 301199927827248443324098746649658754844,
 115532968768857561191818061218773701813,
 191607221511277924288674550230099507123,
 262751473846662512998464656050881387235,
 75522546465545609216785689852028789081,
 306267194955346451948103584154404505794,
 81713620093652557693299093431127237017,
 130346843513741821936326611370813041710,
 267600260873798988999886324804713174180,
 313232226741092686456213541074397062500]

length = 4
collect_list = [""] * len(md5s)

wordlist = list(string.printable)
attack_param = itertools.product(wordlist, repeat=length)
print(collect_list)

for i in attack_param:
    current_word = "".join(i)
    hashed_param = int(md5(current_word.encode("utf-8")).hexdigest(), 16)
    if hashed_param in md5s:
        index = md5s.index(hashed_param)
        print("HIT! -- {} -- md5s[{}]".format(hashed_param, index))
        collect_list[index] = current_word
        print(collect_list)

    if  "" not in collect_list:
        print("WELL DONE!!")
        print("".join(collect_list))
        break

日和ってstring.printableを使ったが、空白文字がFLAGになくてとても残念…。
あと、普通に遅いので計算オーダとかもしっかり考えたい…。

BITSCTF{unc0mpyl3_kn0w5_wh47_1_wr073_s0_s4d}

Programming - Math check

なんかよくわからんから力技で解いた。 与えられるコードは以下。

#include<stdio.h>
#include<string.h>
void main()
{
    char s[23], string[23];
    printf("Enter the flag\n");
    scanf("%s",s);
    int i=1;
    for(int count=1; count<=23; count++)
    {
        i+=A;
        i%=23;
        string[count-1]=s[i];
    }
    
    printf("%s",string);
}

//FUM!SM4GBTD_CT{L4}C0R1I

//A is for you to find. The above is the output.

下のコメントを見る限り、for文内のAという値を書き換えればいいらしい。
あと、その上のコメントが入力値に使えそう、ということで脳死コードを作成した。

#include<stdio.h>
#include<string.h>
int main() {
        char s[23] = {"FUM!SM4GBTD_CT{L4}C0R1I"};
        char string[23];
        printf("Enter the flag\n");
        
        int i=1;
        int inc = 0;

        for(inc; inc <= 100; inc++){
            for(int count=1; count<=23; count++)
            {
                    i+=inc;
                    i%=23;
                    string[count-1]=s[i];
            }
            printf("inc= %d -- %s\n",inc,string);
        }
}

//FUM!SM4GBTD_CT{L4}C0R1I

//A is for you to find. The above is the output.

こいつで書き換える必要のあったAを0~100まで検証できる。
あと変数に直接フラグの鍵になりそうな文字列を代入して標準入力をサボった。
実行してやったら、

inc= 14 -- L4R_M4G1C!}BITSCTF{M0DU
inc= 37 -- L4R_M4G1C!}BITSCTF{M0DU
inc= 60 -- L4R_M4G1C!}BITSCTF{M0DU
inc= 83 -- L4R_M4G1C!}BITSCTF{M0DU

となったので並び替えてフラグゲット!
ブルートフォースといい、力技しかしてねえ。

BITSCTF{M0DUL4R_M4G1C!}

以上です。 新年初CTFでした。
Web問全くできなかったのと、Steg問が途中で躓いてしまったので、 他のwriteup期待です。