TJCTF2018 Write-up

数ヶ月前にCTFを始めたのですが,まだブログでアウトプットしたことがなかったのでTJCTFのWrite-upを書くことにしました.

TJCTF2018

[Web] Blank

Someone told me there was a flag on this site, so why is it that I can only see blank?

  1. デベロッパーツールでページのソースをみる
  2. <!-- flag: tjctf{50urc3_c0d3_n3v3r_l0535} -->

[Miscellaneous] Trippy

trippy

  1. リンク先に飛ぶとgif(?)が置いてある
  2. とりあえずダウンロード
  3. fileコマンドで確かめる
    GIF image data, version 89a, 320 x 240
    本当にgifっぽい
  4. strings be37fef78cfd6c7deda71154f567e6d0cfefbda1f80698c064bab469d3a54c58_trippy.gif | grep tjctfで中身をみる
  5. tjctf{w0w}

[Forensics] Weird Logo

This company's logo stands in contrast of those of other leading edge tech companies with its poor design
Source

  1. リンク先からpngをダウンロード
  2. pngコントラストを調整すると見える
  3. tjctf{in_plain_sight}

f:id:satto1237:20180815222752p:plain f:id:satto1237:20180815222807p:plain

[Miscellaneous] Discord!

Join our Discord for a free flag!

  1. Discordにjoinする
  2. tjctf{d1sc0rd_1s_pr3tty_c0ol}

[Web] Cookie Monster

The Cookie Monster is not a fan of horses

  1. Cookie Monsterということなのでデベロッパーツールを使ってCookieを確認
  2. tjctf{c00ki3s_over_h0rs3s}

[Web] Central Savings Account

I seem to have forgotten the password for my savings account. What am I gonna do? The flag is not in standard flag format.

  1. 適当にpassを入力する
  2. もちろんLogin Failed!
  3. main.jsのソースコードを見てみる
  4. passに対してMD5でハッシュをとってそのハッシュ値698967f805dea9ea073d188d73ab7390と一致するとログイン成功するっぽい
  5. MD5は弱いのでハッシュ値ググるとでてくる
  6. hashtoolkit
  7. tjctf{avalon}
$(document).ready(function() {
    $("#login-form").submit(function() {
        if (md5($("#password").val()).toLowerCase() === "698967f805dea9ea073d188d73ab7390") {
            $("html").html("<h1>Login Succeeded!</h1>");
        }
        else {
            $("html").html("<h1>Login Failed!</h1>");
        }
    })
});

[Cryptography ] Vinegar

I just wanted something more than a Caesar salad. Maybe I should order another one? vinegar.txt

key = Kkkkk kkkkKkkkkkkkkKkkkkkkkkKkk
flag = uucbx{simbjyaqyvzbzfdatshktkbde}
sha256 = 8304c5fa4186bbce7ac030d068fdd485040e65bf824ee70b0bdbac03862bec93

  1. 問題文的にVigenère cipherだと信じる
  2. sha256が怪しい
    多分全探索(ブルートフォース)しろってこと(?)
  3. Vigenère cipherの性質からkeyの長さは9だと決めつける
  4. flagのuucbxは復号化するとtjctfになる(はず)ということを利用してpre_keyを作成
  5. blaisを得る
  6. 残りの4文字を全探索
  7. tjctf{onevinaigrettesaladplease}
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from itertools import product
import hashlib

pre_key = 'blais'
cipher = 'uucbx' + 'simbjyaqyvzbzfdatshktkbde'
plain = 'tjctf'
target = '8304c5fa4186bbce7ac030d068fdd485040e65bf824ee70b0bdbac03862bec93'

def solve_prekey():
    key = ''
    for p, c in zip(plain, cipher):
        for k in range(26):
            if (k + ord(p) - 97) % 26 == (ord(c) - 97) % 26:
                key += chr(97 + k)
                break
    print(key)


def brute_force():
    chars = 'abcdefghijklmnopqrstuvwxyz'
    pws = product(chars, repeat=4)
    for pw in pws:
        key = pre_key + ''.join(pw)
        key *= 3
        key += 'bla'
        flag = ''
        for c,k in zip(cipher, key):
            t = ord(c) - ord(k)
            t %= 26
            t += 97
            flag += chr(t)
        flag = flag[:5] + '{' + flag[5:] + '}'
        sha256 = hashlib.sha256(flag.encode()).hexdigest()
        if sha256 == target:
            print(flag)
            break

if __name__ == '__main__':
    # solve_prekey()
    brute_force()

[Miscellaneous] Interference

I was looking at some images but I couldn't see them clearly. I think there's some interference.

  1. 2枚のpng画像が渡される
  2. 見ただけだと違いがわからない
  3. 画素値を調べる
  4. ところどころ画素値が異なる
  5. 画素値が同じならば白,異なれば黒というルールで画像を生成する
  6. 出来上がった画像を透過させて最初に渡された画像と重ねる(Interferenceなので?)
  7. QRコードがうきでる
  8. tjctf{m1x1ing_and_m4tchIng_1m4g3s_15_fun}
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from PIL import Image
import numpy as np

im1 = Image.open('./v1.png')
im2 = Image.open('./v2.png')

rgb_im1 = im1.convert('RGB')
rgb_im2 = im2.convert('RGB')
size = rgb_im1.size


im3 = Image.new('RGBA',size)

for x in range(size[0]):
    for y in range(size[1]):
        rgb1 = rgb_im1.getpixel((x,y))
        rgb2 = rgb_im2.getpixel((x,y))

        if rgb1 == rgb2:
            im3.putpixel((x,y),(255,255,255,255))

        else :
            print('hoge')
            print('rgb1:{}'.format(rgb1))
            print('rgb2:{}'.format(rgb2))


            im3.putpixel((x,y),(0,0,0,255))

im3.show()
im3.save('./v3.png')

f:id:satto1237:20180815232125p:plain f:id:satto1237:20180815232141p:plain f:id:satto1237:20180815232159p:plain f:id:satto1237:20180815232210p:plain f:id:satto1237:20180815232226p:plain

[Binary Exploitation] Math Whiz

The neighborhood math whiz won't stop bragging about the registration form (source) he coded. Show him who's boss!
nc problem1.tjctf.org 8001

int input(char *str, float f) {

    fgets(str, 16 * f, stdin);

    if (strlen(str) <= 1) {
        puts("No input detected. Registration failed.");
        exit(0);
    } else if (!strchr(str, 10)) {
        while (fgetc(stdin) != 10);
    } else {
        str[strlen(str) - 1] = 0;
    }
}

int main() {
    int admin = 0;
    
    char fullname[16];
    char username[16];
    char password[16];
    char recoverypin[4];
    char email[16];
    char address[16];
    char bio[64];
    
    printf("Full Name: ");
    input(fullname, 1);

    printf("Username: ");
    input(username, 1);

    printf("Password: ");
    input(password, 1);

    printf("Recovery Pin: ");
    input(recoverypin, 4);

    printf("Email: ");
    input(email, 1);

    printf("Address: ");
    input(address, 1);

    printf("Biography: ");
    input(bio, .25);
    
    if (admin) {
        printf("Successfully registered '%s' as an administrator account!\n", username);
        printf("Here is your flag: %s\n", FLAG);
    } else {
        printf("Successfully registered '%s' as an user account!\n", username);
    }

}
  1. プログラムを見た限りadminの値を書き換えるBOFを起こせばflagがとれるっぽい
  2. inputをみてみるとfgetsを使ってるので簡単にBOFできなそう
  3. もう少し詳しくinput見てみると文字列と一緒に係数を渡しおり,係数に応じて入力サイズが決まる
  4. input(recoverypin, 4);が怪しい(char recoverypin[4];なので)
  5. nc problem1.tjctf.org 8001でつなぐ
  6. Recovery Pin:のときにBOFできる程度の文字を打ち込む
  7. Here is your flag: tjctf{d4n63r0u5_buff3r_0v3rfl0w5}

[Reverse Engineering] Python Reversing

Found this flag checking file and it is quite vulnerable
Source

  1. プログラムの意味を考える
  2. どうやらflag1文字ずつに乱数をかけてlmao(定数文字列)とxorをとって2進数に変換してるっぽい
  3. ここで乱数はシード値が設定されてるので再現可能
  4. xorとってるlmaoも再現可能
  5. 3,4の性質を利用して適当にDFSするとflagがとれる
  6. tjctf{pYth0n_1s_tr1v14l}
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import numpy as np
import string

np.random.seed(12345)
other = np.random.randint(1,5,110)
rand = [x for x in other]
ascii = string.ascii_lowercase + string.ascii_uppercase + string.digits + '{}_'
original_target = '1001100001011110110100001100001010000011110101001100100011101111110100011111010101010000000110000011101101110000101111101010111011100101000011011010110010100001100010001010101001100001110110100110011101'
lmao = [ord(x) for x in ''.join(['ligma_sugma_sugondese_'*5])]


def search(search_bit, flag, depth):
    for a in ascii:
        x = (ord(a) * rand[depth]) ^ lmao[depth]
        bit = bin(x)[2:].zfill(8)
        if search_bit.find(bit) == 0:
            if bit == search_bit:
                print(flag + a)
            else :
                search(search_bit[len(bit):], flag + a, depth + 1)

if __name__ == '__main__':
    search(original_target, '', 0)

[Web] Programmable Hyperlinked Pasta

Check out my new site! PHP is so cool!
programmable_hyperlinked_pasta.tjctf.org

  1. サイトにアクセスする
  2. デベロッパーツールでソースコードをみる
  3. <!-- <a href="flag.txt">Here's a flag!</a> -->
  4. 普通にアクセスしようとするとYou don't have permission to access /flag.txt on this server.
  5. よくWebサイトを眺めてみると?lang=en-us.phpphpファイルを呼び出してることに気づく
  6. ?lang=flag.txtでflagを見ようとするがみれない
  7. 1つ上の階層にある可能性があるので?lang=../flag.txtする(ディレクトリトラバーサル)
  8. tjctf{l0c4l_f1l3_wh4t?}

[Cryptography] Classic

My primes might be close in size but they're big enough that it shouldn't matter right?

e = 65537
n = 128299637852747781491257187842028484364103855748297296704808405762229741626342194440837748106022068295635777844830831811978557490708404900063082674039252789841829590381008343327258960595508204744589399243877556198799438322881052857422197506822302290812621883700357890208069551876513290323124813780520689585503
c = 43160414063424128744492209010823042660025171642991046645158489731385945722740307002278661617111192557638773493117905684302084789590107080892369738949935010170735247383608959796206619491522997896941432858113478736544386518678449541064813172833593755715667806740002726487780692635238838746604939551393627585159
  1. RSAを解く問題
  2. とりあえずnをなんとかしないといけないのでfactordb.comに行く
  3. なぜかキャッシュされてる
  4. 終了(多分想定解法ではない?)
  5. p,qを使って秘密鍵を生成してフラグをぬく
  6. tjctf{1_l1ke_squares}
import gmpy
import codecs

e = 65537
N = 128299637852747781491257187842028484364103855748297296704808405762229741626342194440837748106022068295635777844830831811978557490708404900063082674039252789841829590381008343327258960595508204744589399243877556198799438322881052857422197506822302290812621883700357890208069551876513290323124813780520689585503
p = 11326943005628119672694629821649856331564947811949928186125208046290130000912120768861173564277210907403841603312764378561200102283658817695884193223692869
q = 11326943005628119672694629821649856331564947811949928186125208046290130000912216246378177299696220728414241927034282796937320547048361486068608744598351187
C = 43160414063424128744492209010823042660025171642991046645158489731385945722740307002278661617111192557638773493117905684302084789590107080892369738949935010170735247383608959796206619491522997896941432858113478736544386518678449541064813172833593755715667806740002726487780692635238838746604939551393627585159


l = gmpy.lcm(p-1, q-1)
gcd, u, v = gmpy.gcdext(e, l)
m = pow(C, u, N)
print(m)
print(codecs.decode(('%x'%m),'hex_codec'))

[Reverse Engineering] Validator

I found a flag validation program. Do what you want with it.

  1. とりあえずfileコマンドするとELF 32-bit LSB executableだということがわかる
  2. Ubuntuで実行
  3. コマンドライン引数として文字列を渡してあげるとその文字列がフラグどうか判定するという処理をしていることがわかる
  4. もう少し詳しく見るためにIDAになげる
  5. ざっくりまとめると
    1. tjctf{ju57_c4ll_m3_r3v3r53_60d_fr0m_n0w_0n}というフラグを定数として持っている(これが正解のフラグではない)
    2. コマンドライン引数として渡された文字列の長さがフラグと一致するかどうか調べている(strlen)
    3. 長さが一致したらフラグと文字列が完全に一致するか調べている(strcmp)
    4. strcmpで使用している比較対象のフラグは定数として持っていたフラグの一部の文字を書きかえたモノっぽい
  6. strcmpで使っている文字列を確認するためにltraceしてあげる(IDAでも確認できますが少しめんどくさいので)
  7. strcmp("tjctf{ju57_c4ll_m3_35r3v3r_60d_f"..., "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"...) = 1
  8. フラグはtjctf{ju57_c4ll_m3_35r3v3r_60d_fr0m_n0w_0n}

[Binary Exploitation ] Tilted Troop

Can you help us defeat the monster? binary (source) nc problem1.tjctf.org 8002

  1. とりあえずnc problem1.tjctf.org 8002する
  2. コマンドがでてくる
  3. Aでチームメンバを追加して,Fでモンスターと戦って,Qで終了らしい
  4. プログラムを見てみるとチームメンバを追加するたびに0~9までの乱数をチームの強さとして追加している
  5. Fで戦ってチームの強さが400になるとフラグがとれる
    Your team had 3 strength, but you needed exactly 400!
    しかし,プログラム的には8人までしかチームメンバを登録できず,チームの強さは最大でも72までにしかならない
    t.strength = malloc(sizeof(int) * MAX_TEAM_SIZE);
  6. 何らかのBOFをしなければいけない
  7. プログラムを確認するとチームメンバの追加処理の条件式が正しくないため,9人目のチームメンバを入力できることが分かる
  8. aはasciiで97なのでasciiで100になる文字も4文字打ち込めばいいことが分かる(dddd)
  9. tjctf{0oPs_CoMP4Ri5ONs_r_h4rD}
Commands:
A <name> - Add a team member
F - Fight the monster
Q - Quit
if(t.teamSize > MAX_TEAM_SIZE) {
    printf("Your team is too large!\n");
} else {
    t.strength[t.teamSize] = rand() % 10;
    char* newMember = malloc(256);
    strcpy(newMember, &input[2]);
    t.names[t.teamSize] = newMember;
    t.teamSize++;
}
A a
F
Your team had 97 strength, but you needed exactly 400!

[Forensics] Ssleepy

I found this super suspicious transmission lying around on the floor. What could be in it?

  1. .pcapngが渡されるのでとりあえずwiresharkで開く
  2. TCP,FTP,TLSパケットがメイン
  3. とりあえずTCPストリームをみる
  4. 220 Welcome to my super-secure, private-key-transmitting, FTP server とかいってるめちゃ怪しい
  5. RETR key.zipでzipファイル渡してるっぽい
  6. zipを抽出するとserver_key.pemがとれる
  7. TLSパケットがあったのでそのパケットをzipから抽出したサーバ鍵を使って復号してあげる
  8. GET /flag.jpg HTTP/1.1\r\nとか怪しいパケットが走ってることがわかる
  9. SSLストリームをみる
  10. .jpgを抜くためにSSLストリームをRaw形式で保存
  11. foremostなどで.jpgを抽出するとフラグがとれる
  12. TJCTF{WIRESHARK_OR_SHARKWIRE?}

f:id:satto1237:20180816003727j:plain

Ess Kyoo Ell

Find the IP address of the admin user! (flag is tjctf{[ip]})

  1. Web問なのでとりあえずWebサイトにアクセスする
  2. 登録フォームがでてくるのでmail addressとpasswordを打ち込んでSign inする
  3. This is what I got about you from the database: no such column: password

    passwordというカラムがないぞと怒られる
    怪しい

  4. POSTで色々送ってみる
    curl https://ess-kyoo-ell.tjctf.org/ -X POST -d 'email=a@a&password=hoge'

    This is what I got about you from the database: no such column: password

    curl https://ess-kyoo-ell.tjctf.org/ -X POST -d 'email=a@a'

    This is what I got about you from the database: 'NoneType' object is not iterable

    ここでデータベースにありそうなカラムを予測する(例えばusernameとか)
    adminのip addressがフラグなのでusername=adminでadmin情報を抜いてみる
    curl https://ess-kyoo-ell.tjctf.org/ -X POST -d 'username=admin'

    This is what I got about you from the database: {'id': 706, 'username': 'admin', 'first_name': 'Administrative', 'last_name': 'User', 'email': '[email protected]', 'gender': 'Female', 'ip_address': '145.3.1.213'}

    あった

  5. tjctf{145.3.1.213}