乙Py先生のプログラミング教室

乙Py先生のプログラミング教室
初学者のためのプログラミング学習サイト

今回はCTFの問題を解いてみました

解説動画はこちら



CTFとは

さて、まずCTFとは何でしょうか?

CTF(Capture The FLAG )
のことで情報セキュリティの分野で
専門知識や技術を駆使して
隠されているFlag(答え)を見つけ出し
時間内に獲得した合計点数を競う
ハッキングコンテストのことになります

クイズ形式の問題の他
疑似的な攻防戦も有り
以下のような技術が必要となってきます

・ネットワーク
  通信技術、トラフィックのキャプチャ
・フォレンジクス
  情報の秘匿、ログ解析、ファイルフォーマット、データの復元
・Web技術    
  Webアプリケーションの脆弱性、データベースアクセス
・プログラミング
  プログラミング言語、組み込み技術、リバースエンジニアリング
・暗号化技術
  符号化、公開鍵基盤(PKI)
・脆弱性調査
  バグ、攻撃コードの送信

この問題が解ける様になると
次のような技術向上のメリットがあります
 情報セキュリティに対する知識が身につく
 情報セキュリティに限らない広範囲の知識が身につく
 実践的なスキルが身につく
 トラブルシューティングのカンが身につく


世界中でこういった問題が
解けるサイトがありますが
日本にも問題が解けるサイトがあるので
今回はこちらの問題を拝借いたします

https://ctf.cpaw.site/index.php
なお問題を見るにはアカウントが必要です


[Crypto] 1
古典暗号文解読の問題です

シーザー暗号というものがあります
これは元の平文を何文字かシフトさせたものです

アルファベットでシーザー暗号を考えると
平文 : abc

3文字シフト

暗号文 : def

のようになります

平文を暗号化、暗号を平文にするのも
同じプログラムで出来るので
シーザー暗号を作るプログラムを考えれば
この問題を解けます

このようなコードで暗号解読できます
st = 'fsdz{Fdhvdu_flskhu_lv_fodvvlfdo_flskhu}'

def caesar_cipher_encrypt(plaintext , key):
    cipher = ""
    for p in list(plaintext):
        if 'A' <= p <= 'Z':
            cipher += chr((ord(p) - ord('A') + key)%26 + ord('A'))
        elif 'a' <= p <= 'z':
            cipher += chr((ord(p) - ord('a') + key)%26 + ord('a'))
        else:
            cipher += p
    return cipher
    
for i in range(20,26):
    print(i,caesar_cipher_encrypt(st , i))




[PPC]

並べ替えの問題です

配列の中身を大きい順に並べ替えて
くっつけて 並べ替えた後の値 
がフラグになります

配列のソート
数値の文字列化
文字列の連結

これが出来ればこの問題が解けるので
プログラムに関わらず
エクセルやテキストエディターなどでも
解くことは出来ると思います

一応この問題を解くコードはこれです
data = [15,1,93,52,66,31,87,0,42,77,46,24,99,10,19,36,27,4,58,76,2,81,50,102,33,94,20,14,80,82,49,41,12,143,121,7,111,100,60,55,108,34,150,103,109,130,25,54,57,159,136,110,3,167,119,72,18,151,105,171,160,144,85,201,193,188,190,146,210,211,63,207]

txt = 'cpaw{{{0}}}'
txt = txt.format(''.join(map(str,sorted(data,reverse=True))))
print(txt)




[Forensics] 

画像解析の問題です

画像ファイルには位置情報など
様々な情報(Exif情報)が
付加されることがあるので

そこから撮影場所を
特定することも出来たりします

写真に写っている川の名前を特定する問題で
画像名は river.jpg です

画像をみたい方は問題のサイトに
アクセスして手に入れて頂くか
動画の方をみてくださいませ


画像を見るコードはこれです
from PIL import Image
import PIL.ExifTags as ExifTags
import matplotlib.pyplot as plt

file_path = 'river.jpg'

img = Image.open(file_path)

plt.imshow(img)
plt.axis('off')
plt.show()

ここから画像のExif情報を抽出します
Exif情報はかなりたくさんの項目がありますが
GPSの緯度経度を取得します

画像のExif情報を取得するコード
exif = {}
exif = img._getexif()
for k, v in exif.items():
    if k==34853:
        for gk,gv in v.items():
            print(gk,gv)
0 b'\x02\x03\x00\x00'
1 N
2 (31.0, 35.0, 2.76)
3 E
4 (130.0, 32.0, 51.7272)



緯度・経度は一般に度・分・秒で表します
分,秒は時間と同じように60進法になっています
秒に小数点以下の数値をつける場合は
秒の後ろに10進法で表します


ExifのGPSは度分秒なので
10進法にするには変換が必要です
deg , minu , sec = 31.0, 35.0, 2.76
N = deg + minu/60.0 + sec/3600.0

deg2 , minu2 , sec2 = 130.0, 32.0, 51.7272
E = deg2 + minu2/60.0 + sec2/3600.0

print('{0},{1}'.format(N,E))
31.5841,130.547702


あとはこの緯度経度を
地図に打ち込んで見てみましょう

そうすると
スクリーンショット 2022-11-12 10.03.50

こんな場所でした

拡大してみると
スクリーンショット 2022-11-12 10.04.13

鹿児島県にある川の
陸橋の上あたりから
取られた写真であることが分かります

この川の名前が答えですね




[Crypto] 2

最後は
次のC言語のコードを実行すると
回答が得られる問題です

元のCのプログラムを見てみると
暗号を平文に直すアルゴリズムのようでした

C言語が分かる方は
そのまま実行すれば答えば出てきます

C言語が分からない人は
他の言語に直すのもアリです

Pythonに直してみると
次の様なコードになります

flag = 'ruoYced_ehpigniriks_i_llrg_stae'

for key in range(2,7):
    answer = ''
    for i in range( key - 1, len(flag)+1,key):
        for j in range(i , i-key , -1):
            idx = j
            if j < len(flag):
                answer+=flag[idx]

    print(x , 'cpaw{{{0}}}'.format(answer))


まとめ
CTFには
暗号解読やフォレンジックスなど
プログラミングで解ける問題も
多く含まれています

プログラミング技術の向上意外にも
セキュリティー知識を身に付けるための
問題が数多くあるので
これを解ける様になると
セキュリティーに関する技術力の
向上が見込めると思います

それでは

今回は神経衰弱を一人で行う
シミュレーションプログラムを考えてみました


解説動画はこちら



神経衰弱

今回は神経衰弱です

トランプで行うゲームのひとつで
カードを伏せた状態で混ぜ
プレイヤーは好きな2枚を
その場で表に向けます

2枚が同じ数字であれば
それらを得ることができ
もう一度プレイできます

2枚が異なる数の場合
カードを元通りに伏せて
次のプレイヤーの順番に

すべてのカードが取られるまで行い
取ったカードの枚数が多い
プレイヤーの勝ちになります


今回は友達がいないので
一人で神経衰弱をしてみる事を考えてみます

何ターンあれば全部空けられるのか?
仮想のカードを作って
シミュレーションしてみましょう


考え方

最初はデッキを用意して
52枚をシャッフルします
# 52枚のカードのデッキ
deck = [i for i in range(1,14)] * 4

import numpy as np

# シャッフルする
np.random.shuffle(deck)
print(deck)
[1, 11, 7, 6, 11, ・・・]

こんな感じで52枚分のデータを用意します


ターン時の行動を考えてみましょう

こんな感じでしょうか?

カードを1枚開ける
  知ってる数字があればそれを開ける
カードをもう1枚開ける
  同じ数字なら取る 次の行動へ
  違う数字ならターンプラス1
2枚ペアが判明していたらそれを開ける

ここら辺の行動を関数にまとめて
ゲームできる様にしてみました


ゲーム関数

import numpy as np
import collections

# under : カードを覚えておくための変数

# 2個以上分かったカードがあるかどうか
def check_2(under):
    c = collections.Counter(under.values())    
    for i in range(1,14):
        if c[i]>=2:
            return i
    return 0

# 2個のペアのインデックスを返す
def get_2pair(under , num):
    pair = [k for  k , v in under.items() if v == num]
    return pair

# ペアカードを取る
def get_card(under , pair):
    for i in pair:
        under[i] = 99
    return under

# 開けていないカードを1枚選択する
def open_1card(under):
    # 候補 0:未開、1-13:開封済、99:取得済
    cand = [k for k,v in under.items() if v==0]
    return np.random.choice(cand)

# 数字が判明しているかをチェック
def under_check(under,num,select):
    # 候補 0:未開、1-13:開封済、99:取得済
    cand = [k for k,v in under.items() if v==num and k!=select]
    if len(cand)>0:
        return True
    else:
        return False
    
# 数字が判明しているものを選択する
def open_under_card(under,num,select):
    # 候補 0:未開、1-13:開封済、99:取得済
    cand = [k for k,v in under.items() if v==num and k!=select]
    return np.random.choice(cand)

# ログのプリント
def logs(turn , gets , sheets , num):
    text = 'ターン : {0:02} , 取得済み : {1:02} , {2}枚目 , 数字:{3:02}'.format(turn , gets , sheets , num)
    print(text)
# 神経衰弱ゲームをシミュレーションする
def game():
    
    # デッキを作ってシャッフルする
    deck = [i for i in range(1,14)] * 4
    np.random.shuffle(deck)
    
    # 回数と開けたカードの記憶
    turn , gets , under = 1, 0 , {i:0 for i in range(52)}
    
    # ゲーム開始
    for j in range(100):
        # 上がりの判定
        if all([i==99 for i in under.values()]):
            print('上がり , turn : {0:02}'.format(turn))
            break
        
        #2枚開いたのがあればそれを消す
        pair_num = check_2(under)
        if pair_num > 0:
            print('判明済み有り : {0:2}'.format(pair_num))
            # ペアを開きに変える
            pair = get_2pair(under , pair_num)
            under = get_card(under , pair)
            gets +=1
            print('get card : {0:02},{1:02} , 数字 : {2:02}'.format(pair[0] , pair[1] , pair_num))

        else:
            #1枚めくる
            select_one = open_1card(under)
            # 開けたカードの数字
            num_one = deck[select_one]
            # 数字を覚えておく
            under[select_one] = num_one
            logs(turn , gets , 1 , num_one)
            
            #数字が分かっているものがあればそれを取る
            if under_check(under,num_one,select_one):
                target = open_under_card(under , num_one , select_one)
                under[select_one] , under[target] = 99 , 99
                print('判明済み有り : {0:2}'.format(num_one))
                logs(turn , gets , 2 , num_one)
                gets +=1
                print('get card : {0:02},{1:02} , 数字 : {2:02}'.format(select_one , target , num_one))

            else:
                #分かってない場合はもう一枚めくる
                select_2nd = open_1card(under)
                num_2nd = deck[select_2nd]
                # 数字を覚えておく
                under[select_2nd] = num_2nd               
                logs(turn , gets , 2 , num_2nd)
   
                #同じ数字ならカードを取る
                if num_one==num_2nd:
                    under[select_one] , under[select_2nd] = 99 , 99
                    gets +=1
                    print('get card : {0:02},{1:02} , 数字 : {2:02}'.format(select_one,select_2nd,num_one))
                else:
                    #違う数字ならターンプラス1
                    turn +=1
                    print('ターン終了')
                    
    return turn
    
game()

ターン : 01 , 取得済み : 00 , 1枚目 , 数字:04
ターン : 01 , 取得済み : 00 , 2枚目 , 数字:02
ターン終了
ターン : 02 , 取得済み : 00 , 1枚目 , 数字:07
ターン : 02 , 取得済み : 00 , 2枚目 , 数字:05
ターン終了
ターン : 03 , 取得済み : 00 , 1枚目 , 数字:09
ターン : 03 , 取得済み : 00 , 2枚目 , 数字:02
ターン終了
判明済み有り :  2
get card : 08,51 , 数字 : 02
ターン : 04 , 取得済み : 01 , 1枚目 , 数字:10
ターン : 04 , 取得済み : 01 , 2枚目 , 数字:09
ターン終了
判明済み有り :  9
get card : 29,34 , 数字 : 09
ターン : 05 , 取得済み : 02 , 1枚目 , 数字:07
判明済み有り :  7
ターン : 05 , 取得済み : 02 , 2枚目 , 数字:07
get card : 06,22 , 数字 : 07
ターン : 05 , 取得済み : 03 , 1枚目 , 数字:13
ターン : 05 , 取得済み : 03 , 2枚目 , 数字:03
ターン終了
ターン : 06 , 取得済み : 03 , 1枚目 , 数字:08
ターン : 06 , 取得済み : 03 , 2枚目 , 数字:08
get card : 37,31 , 数字 : 08
ターン : 06 , 取得済み : 04 , 1枚目 , 数字:01
ターン : 06 , 取得済み : 04 , 2枚目 , 数字:08
ターン終了
ターン : 07 , 取得済み : 04 , 1枚目 , 数字:06
ターン : 07 , 取得済み : 04 , 2枚目 , 数字:07
ターン終了
ターン : 08 , 取得済み : 04 , 1枚目 , 数字:12
ターン : 08 , 取得済み : 04 , 2枚目 , 数字:05
ターン終了
判明済み有り :  5
get card : 09,48 , 数字 : 05
ターン : 09 , 取得済み : 05 , 1枚目 , 数字:12
判明済み有り : 12
ターン : 09 , 取得済み : 05 , 2枚目 , 数字:12
get card : 28,38 , 数字 : 12
ターン : 09 , 取得済み : 06 , 1枚目 , 数字:08
判明済み有り :  8
ターン : 09 , 取得済み : 06 , 2枚目 , 数字:08
get card : 19,46 , 数字 : 08
ターン : 09 , 取得済み : 07 , 1枚目 , 数字:03
判明済み有り :  3
ターン : 09 , 取得済み : 07 , 2枚目 , 数字:03
get card : 03,26 , 数字 : 03
ターン : 09 , 取得済み : 08 , 1枚目 , 数字:04
判明済み有り :  4
ターン : 09 , 取得済み : 08 , 2枚目 , 数字:04
get card : 30,21 , 数字 : 04
ターン : 09 , 取得済み : 09 , 1枚目 , 数字:13
判明済み有り : 13
ターン : 09 , 取得済み : 09 , 2枚目 , 数字:13
get card : 13,25 , 数字 : 13
ターン : 09 , 取得済み : 10 , 1枚目 , 数字:05
ターン : 09 , 取得済み : 10 , 2枚目 , 数字:05
get card : 17,18 , 数字 : 05
ターン : 09 , 取得済み : 11 , 1枚目 , 数字:01
判明済み有り :  1
ターン : 09 , 取得済み : 11 , 2枚目 , 数字:01
get card : 01,23 , 数字 : 01
ターン : 09 , 取得済み : 12 , 1枚目 , 数字:11
ターン : 09 , 取得済み : 12 , 2枚目 , 数字:04
ターン終了
ターン : 10 , 取得済み : 12 , 1枚目 , 数字:01
ターン : 10 , 取得済み : 12 , 2枚目 , 数字:13
ターン終了
ターン : 11 , 取得済み : 12 , 1枚目 , 数字:09
ターン : 11 , 取得済み : 12 , 2枚目 , 数字:11
ターン終了
判明済み有り : 11
get card : 11,15 , 数字 : 11
ターン : 12 , 取得済み : 13 , 1枚目 , 数字:03
ターン : 12 , 取得済み : 13 , 2枚目 , 数字:01
ターン終了
判明済み有り :  1
get card : 35,45 , 数字 : 01
ターン : 13 , 取得済み : 14 , 1枚目 , 数字:02
ターン : 13 , 取得済み : 14 , 2枚目 , 数字:10
ターン終了
判明済み有り : 10
get card : 20,24 , 数字 : 10
ターン : 14 , 取得済み : 15 , 1枚目 , 数字:06
判明済み有り :  6
ターン : 14 , 取得済み : 15 , 2枚目 , 数字:06
get card : 10,49 , 数字 : 06
ターン : 14 , 取得済み : 16 , 1枚目 , 数字:12
ターン : 14 , 取得済み : 16 , 2枚目 , 数字:11
ターン終了
ターン : 15 , 取得済み : 16 , 1枚目 , 数字:10
ターン : 15 , 取得済み : 16 , 2枚目 , 数字:03
ターン終了
判明済み有り :  3
get card : 12,32 , 数字 : 03
ターン : 16 , 取得済み : 17 , 1枚目 , 数字:04
判明済み有り :  4
ターン : 16 , 取得済み : 17 , 2枚目 , 数字:04
get card : 39,14 , 数字 : 04
ターン : 16 , 取得済み : 18 , 1枚目 , 数字:12
判明済み有り : 12
ターン : 16 , 取得済み : 18 , 2枚目 , 数字:12
get card : 40,50 , 数字 : 12
ターン : 16 , 取得済み : 19 , 1枚目 , 数字:11
判明済み有り : 11
ターン : 16 , 取得済み : 19 , 2枚目 , 数字:11
get card : 41,47 , 数字 : 11
ターン : 16 , 取得済み : 20 , 1枚目 , 数字:07
判明済み有り :  7
ターン : 16 , 取得済み : 20 , 2枚目 , 数字:07
get card : 43,05 , 数字 : 07
ターン : 16 , 取得済み : 21 , 1枚目 , 数字:06
ターン : 16 , 取得済み : 21 , 2枚目 , 数字:10
ターン終了
判明済み有り : 10
get card : 42,44 , 数字 : 10
ターン : 17 , 取得済み : 22 , 1枚目 , 数字:09
判明済み有り :  9
ターン : 17 , 取得済み : 22 , 2枚目 , 数字:09
get card : 27,16 , 数字 : 09
ターン : 17 , 取得済み : 23 , 1枚目 , 数字:13
判明済み有り : 13
ターン : 17 , 取得済み : 23 , 2枚目 , 数字:13
get card : 02,07 , 数字 : 13
ターン : 17 , 取得済み : 24 , 1枚目 , 数字:02
判明済み有り :  2
ターン : 17 , 取得済み : 24 , 2枚目 , 数字:02
get card : 00,33 , 数字 : 02
ターン : 17 , 取得済み : 25 , 1枚目 , 数字:06
判明済み有り :  6
ターン : 17 , 取得済み : 25 , 2枚目 , 数字:06
get card : 36,04 , 数字 : 06
上がり , turn : 17

こんな感じで26ペアを取れた時点で
ゲームは終了です

多分合ってるとは
思うんですけどねー

大したデバッグはしていないので
間違いがあったらごめんなさいね



1000回でシミュレーション

最後にこれを1000回繰り返して
何手で上がれたかのシミュレーションです

ログを抜いたgame2関数を用意して
1000回実行した際の
結果をまとめてみました

calc = {}
for i in range(1000):
    key = game2()
    if key in calc:
        calc[key]+=1
    else:
        calc[key] =1
for k,v in sorted(calc.items()):
    print('ターン数 : {0:02} , {1:03}回'.format(k,v))
ターン数 : 11 , 001回
ターン数 : 13 , 004回
ターン数 : 14 , 041回
ターン数 : 15 , 149回
ターン数 : 16 , 266回
ターン数 : 17 , 295回
ターン数 : 18 , 168回
ターン数 : 19 , 059回
ターン数 : 20 , 016回
ターン数 : 21 , 001回

これでみると
大体17回もあれば
上がれるみたいですねー

ほぼほぼ20回以内に
上がれているので
記憶力があれば
20ターンもかからないようです

今回は神経衰弱を
シミュレーションしてみました

それでは



今回はpythonで簡単に音声を合成できる
pyopenjtalkを使ってみました

解説動画はこちら


 

今回使用したpyopenjtalkは
日本語の文章を音素表記に
変換してくれる便利ツールです

普通のWindowsPCだと
インストールにコツがいるかもしれません

MacやLinuxは大丈夫っぽいので
Google Colabで実行できるコードを用意しました

colabだと入っていないので
まずはインストールが必要です



インストール方法
こちらのコードでインストールできます
pip install pyopenjtalk



音素表記へ変換

次は日本語のテキストを
音素表記に変換してみましょう

日本語テキストを入力してあげると
簡単に変換できます

import pyopenjtalk

talk_text = 'こんにちは'
voice = pyopenjtalk.g2p(talk_text)
print(voice)
k o N n i ch i w a




音声ファイルに変換する

次は日本語のテキストを
音声にしてみましょう

ファイル化することもできるので
colab上に音声ファイルとして出力します

displayを使うと
colab上で音声を再生することもできます
import pyopenjtalk
import numpy as np
from scipy.io import wavfile

taik_text = "どうもこんにちは 乙ぱいです"
x , sr = pyopenjtalk.tts(taik_text)
wavfile.write("output.wav", sr , x.astype(np.int16))

# 音声ファイルの再生
import IPython.display
IPython.display.Audio("output.wav" , autoplay=True )



ファイル化せず直接喋らせてみる

pyopenjtalkでは音声データとして
周波数と数値配列を生成するようです

displayを使えば
ファイル化しなくても
再生できます
import IPython.display
import pyopenjtalk
from scipy.io import wavfile

taik_text = "どうも、ゆっくり不動産です"
#taik_text = "じょ、女子高生が、す、好きなんだなぁ"
#taik_text = "先にシャワー浴びてこいよ"
#taik_text = "この紋所が目に入らぬか!"
#taik_text = "事件は会議室で起きてるんじゃない 現場で起きてるんだ!"
#taik_text = "室井さん、レインボーブリッジ封鎖できません"
#taik_text = "じっちゃんのなににかけて"

x , sr = pyopenjtalk.tts(taik_text)
IPython.display.Audio(x , rate=sr , autoplay=True)

合成した音声は
動画の方で聞いてみてください

日本語の小説など
大量の文章を音声化したいなら
少しは使えるかも知れませんね

動画作成の自動化なんかも
うまくやれば出来てしまうかもしれません

なかなか面白いツールでした
それでは

このページのトップヘ