俺的 Capture The Flag Writeup

Capture The Flag に挑戦したときの記録を自分用にドキュメント化して置いておくだけのブログ。

[CpawCTF] 第二夜: Q6 古典的暗号

Q6.[Crypto] Classical Cipher

CpawCTF Level1 Q6 (通し番号は 6 だけど 第二問目)

以下、問題文

Q6.[Crypto] Classical Cipher

10pt

暗号には大きく分けて、古典暗号と現代暗号の2種類があります。
特に古典暗号では、古代ローマの軍事的指導者ガイウス・ユリウス・カエサル(英語読みでシーザー)が初めて使ったことから、名称がついたシーザー暗号が有名です。これは3文字分アルファベットをずらすという単一換字式暗号の一つです。次の暗号文は、このシーザー暗号を用いて暗号化しました。暗号文を解読してフラグを手にいれましょう。

暗号文: fsdz{Fdhvdu_flskhu_lv_fodvvlfdo_flskhu}

答案

問題文を通読すると、以下の情報が手に入りました。

  • 暗号文: fsdz{Fdhvdu_flskhu_lv_fodvvlfdo_flskhu}
  • この暗号文は、「シーザー暗号」を用いて暗号化しているということ。
  • 「シーザー暗号」は「3文字分アルファベットをずらす」という単一換字式暗号により暗号化を行なっているということ。

さて、暗号文を手作業で (頭の中でカウントして) 3文字分だけずらしても良いと思いますが、一応プログラムを書く技能を持っているので、ウォーミング・アップがてら、プログラムを組んでみましょう。

復号プログラム

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

int main() {

  char cip[] = "fsdz{Fdhvdu_flskhu_lv_fodvvlfdo_flskhu}";
  int l;

  l = strlen(cip);

  printf("Cipher: %s (length: %d)\n", cip, l);

  for (int i=0; i<l; i++) {
    if ( ('A' <= cip[i] && cip[i] <= 'Z') || ('a' <= cip[i] && cip[i] <= 'z') ) {
      //cip[i] += 3;
      cip[i] -= 3;
    }
  }

  l = strlen(cip);
  printf("Decryption: %s (length: %d)\n", cip, l);

  return 0;
}

一応、解説がてら、以下にメモを残しておきます。

標準入出力を扱うため stdio.h を、文字列を扱いやすくするため string.hincludeしています。

cip[] = "暗号文" の部分は文字列リテラルを用いて、暗号文を変数として定義しています。

数えるのが面倒なので、配列のサイズは省略 (自動決定)

文字列リテラルの扱いに関しては こちら を参考にさせていただきました。

そしてデバッグのために、保険で文字数を strlen() 関数を使ってカウントしておきます。( 参考 )

for 文 により、cip の各文字に対して処理を行います。

その最中、if 文 により「アルファベットに対してのみ」3文字ズラす対象とするようにし、最後に復号した暗号を出力します。

さて、アルファベットの部分を捉えることができたら、これを 3文字だけずらします。

最初、3文字後ろ ( cip[i] += 3 )に動かしていたのですが、意味がわからない文が出てきまして、試しに3文字前 ( cip[i] -= 3 ) に動かしたら、以下の意味を持つような文字列が得られました。

後から気軽にやり直せるのは、プログラムのいいところだと思います。

Cipher: fsdz{Fdhvdu_flskhu_lv_fodvvlfdo_flskhu} (length: 39)
Decryption: cpaw{Caesar_cipher_is_classical_cipher} (length: 39)

cpaw{Caesar_cipher_is_classical_cipher} がどうやら Flag のようです。

「シーザー暗号は古典暗号」の意

ちなみに Cipher は「暗号」, Decryption は「復号」です。

submit

Flag をゲットしたので、cpawctf のサイトに提出します。

f:id:comeonknowhow:20200515001014p:plain
submit_flag

無事、クリアできたようです。

f:id:comeonknowhow:20200515001044p:plain
accepted

どうでも良いことなのですが

f:id:comeonknowhow:20200515001146p:plain
number

前回の Test problem は 7000番台でしたよね。 今回は 5900 近くということで、1000人くらいは登録して、テストだけして、って感じなのでしょうか。

私もよく三日坊主するので、お気持ちはよくわかります。(早すぎる共感)

まとめ

久しぶりに C言語でプログラムを (しかも文字列を扱う) 書いたので、思ったよりコストがかかってしまいました。

いい勉強になったと思います。

研究の分野柄 python を書くことが多いのですが、C を書くと型の硬さや、ビルトインの関数, ヘッダの include などにおっとっととなりますね。

ところで私は、暗号といえば、サマーウォーズを思い出します。

「骨折り損のくたびれもうけ」でしたっけ。暗号を考える人はとてもユーモアに飛んでいると思います。

次の問題は、拡張子の特定のようです。それでは、また。

Links

別解

暗号文を格納した変数 cip のうち、アルファベットを捉える if 文 に関して、以下のような書き方をしても動作することを確認しています。

あらかじめ文字列の中に出てくるアルファベット以外を、自分の目でみて、除外する方針です。

  for (int i=0; i<l; i++) {
    if ( (cip[i] == '{') || (cip[i] == '}') || (cip[i] == '_')){
      continue;
    } else {
      //cip[i] += 3;
      cip[i] -= 3;
    }