スプーキーズのちょっとTech。

SPOOKIES社内のより技工的な、専門的なブログページです。

「Type」ScriptでHelloWorldをしてみた Part1

やあこんにちは。🌱だよ。皆さんはこのポストをご覧になったでしょうか?

この投稿の画像、頭で解けた人はいますでしょうか? 実はこれ、最終的には「Hello_Type_World」という文字が推論されるんです! すごいでしょ

今回は上のコードが主に題材となっています!

TypeScriptには俗に言われている「型パズル」というのが存在しています。 が、今回は特別に...!? 「型パズル」ではなく「型プログラミング」をしてみたいと思います。つまり型だけでプログラミングをするんです。処理は書いちゃいけません!

やはり型プログラミングをするうえで一番最初にするのは誰しもやってきた「Hello World」の出力でしょう。 ですが、今回は「型」が題材なので、「出力」ではなく「推論」をしていきます。 もちろん文字列で単にHellow Worldをするのではなく、TypeScriptの「Type」をフルに使った正真正銘の「Type」ScriptでHello Worldをやっていこうと思います!!!!

結果だけ知りたい方のために最終的にはこのコードはこちらです!

type Result = TupleJoin<[
  Capitalize<TupleJoin<[h, e, l, l, o]>>,
  sp,
  Capitalize<TupleJoin<[w, o, r, l, d]>>,
  ex
]>;

image.png (15.1 kB)

ね、簡単でしょう?

え...難しい?じゃあ、順を追ってちゃんと解説していきます!

実行環境

  • TypeScript v5.9.3
  • Target/Module: ESNext
  • TypeScript Playground 標準設定

ルール

  • typeのみ定義可能(つまり関数/値の定義禁止)
  • 文字テーブル以外のlength > 0の直接的な文字リテラルの禁止
  • テンプレートリテラルにおいてプレースホルダー以外の使用を禁止
  • 直接的な数値リテラルの禁止
  • 型定義時のジェネリクス引数のextends及びデフォルト値の禁止
    • type Type<T extends string> みたいな表現ができない代わりにtype Type<T> = T extends stringにしてねという意味
  • object型({ key: value })の定義禁止
  • 文字テーブルを使用し、Resultが"Hello World!"という文字リテラルで推論されていること

説明

1. "length"を作る

文字リテラルが作れない以上"length"は作れないです。 なんで"length"を作らなきゃいけないのか?というと、これはTuple型系の推論をする際に「length」というのはとても重要になります。

type TupleLEngth = [a, b, c, d]["length"];

この場合、TupleLengthは4の数値リテラルを返してくれます。これはTuple型というTypeScriptにある特殊な型形態だからこそできる推論で、基本的にArray.prototype.lengthは静的に要素数を推論できないためnumberを推論しますが、TypeScriptのTuple型は「要素数が必ず固定になっている配列型」という解釈をするため、必ずlengthが一定になる=lengthが数値リテラルで推論できるわけです。

そしてこの推論をするために"length"が必要となるわけですね。

1-1. 標準APIから文字リテラルを抽出する!

しょっぱなから何言ってる変わらないと思いますが、標準APIは文字リテラルの宝庫です。 つまるところ標準APIのプロパティ名や関数名を抽出できれば文字リテラルを定義することなく手文字リテラルを作れちゃうわけですね。

主にここでキーとなってくるのはこの構文です。

type Key = keyof { aaa: 0, bbb: 1 }; // 'aaa' | 'bbb'

keyofはそのオブジェクトのプロパティ名としてアクセス可能なものを文字リテラルのUnionとして返してくれます。 つまりこれで文字リテラルが手に入るわけですね。すばらしい

ただちょっと待って!これはUnionです。つまり完全にその文字リテラルを保証してくれるわけではありません。 つまりUnionじゃなければいいんです。 じゃあどうすればいいのか?オブジェクトを定義する?でも今回のルールでは新規オブジェクトの定義はできません。 オブジェクトからそのプロパティだけを抽出しちゃいましょう!

type IsAny<T> = true extends false & T ? true : false;
type Equal<L, R> = [R] extends [L] ? [L] extends [R] ? true : false : false;
type PickKey<T, P> = {
  [K in keyof T]: IsAny<T[K]> extends false
    ? Equal<T[K], P> extends true
      ? K
      : never
  : never
}[keyof T];

やってることを端的にいうと、オブジェクト内にあるプロパティの型が指定された型に一致するプロパティの値をプロパティ名として、オブジェクトのキーから値(プロパティ名)だけを取り出すということをしています。

例えば、PickKey<typeof Object, typeof Object.defineProperty>とすると"defineProperty"が返ってきます これはtypeof ObjectObjectConstructorを型として、typeof Object.defineProperty<T>(o: T, p: PropertyKey, attributes: PropertyDescriptor & ThisType<any>) => Tなので、この型に合って(Equal)かつAnyではないプロパティを探します。

型のEqualって何ぞやって感じですが、B extends Aは数学の集合的な挙動をして、集合記号的にいうとB⊆Aなので、プログラム的に直すとB <= Aです。つまりその逆であるB >= Aをすると、B <= A && B >= Aで、B == Aと同じような意味合いを持つことができます。 しかし、どちらかがUnionである場合注意が必要で、TypeScriptではextendsを通るときにUnion分散というのが行われます。例えばEqual<A | B, A>とすると推論されるのはbooleanとなります。(A==Aであり、A==Bでもあるため、true | falseとなり、結果booleanと解釈される) それを防ぐには、Tuple型に内包して定義をすると防げます。なので、[R] extends [L] ? [L] extends [R]となっているわけですね。

あと、IsAnyで防いでいるのは単にAnyが特殊だからです。 anyというのはTypeScriptにおいては絶対王者と言わんばかりの強制力を持ちます。

type A = 0 & any; // any
type B = 0 & unknown; // 0

anyunknownと違い、「推論を放棄する」というのに近い意味を持つもので、放棄された推論は基本的に何してもanyとなるという挙動をします。 なのでextendsを通しても基本的には必ずtrueになるような挙動を示すため、それを逆手にとってtrue extends false & Tと置くと、any以外は基本的にfalseになります。なのでいい感じにAnyかどうかを判別できるわけです。

1-2. プリミティブから文字リテラルを抽出する!

特殊なプリミティブ型はテンプレートリテラルに通すと文字リテラルを取得できます。 主にtrue, false, null, undefinedの4つは文字リテラルとして推論してくれます。

type True = `${true}`; // "true"
type False = `${false}`; // "false"
type Null = `${null}`; // "null"
type Undefined = `${undefined}`; // "undefined"

1-3. 最初の文字と最後の文字を抜き出す!

さて、ここまで来たら文字を加工する工程になります。 いろんなところから文字リテラルは推論できましたが、lengthを作るためにはちゃんと"length"という文字リテラルを生み出さなくてはなりません。

なので、推論した文字リテラルの一部を拝借できるようにしましょう!

type GetFirstChar<S> = S extends `${infer First}${string}`
  ? First
  : never;
type GetLastChar<S> = S extends `${infer First}${infer Remain}`
  ? Remain extends ''
    ? First
    : GetLastChar<Remain>
  : never;

実はテンプレートリテラルはextendsとinferを使うと面白いことができて、特定の文字リテラルの一部をinferで使用できちゃうんですね~

なので、${infer First}${string}とテンプレートリテラルに入れると、Firstは最初の1文字、その他は何かしらの文字列(string)というextendsができて、Firstを返してあげると最初の1文字だけ返ってくるわけです。

type A = GetFirstChar<"First">; // "F"

最後の文字を取得したいときは、型を再帰的に推論してあげるとできるようになります。 「最初の文字を取り出す」という動作で残った文字列リテラルが、もし空文字列であるならば「それ以上文字がない」ということになるので、取り出した文字を返します。まだ文字が存在するなら、残った文字リテラルで同じ処理を繰り返すといった動作をすることで、結果的に最後の文字が取り出せるというわけです。

// 関数でいうとこういう感じになる
function GetLastChar(S: string) {
  const First = S[0];
  const Remain = S.slice(1);
  if (First !== undefined && Remain !== undefined) {
    if (Remain === "") {
      return First;
    } else {
       return GetLastChar(Remain);
    }
  } else {
    throw;
  }
}

なぜこんな推論ができるかというと、テンプレートリテラルには特殊な推論ルールがあって、以下のルールで推論されるからです。

各型式(infer Tstring)を推論元となる文字列内の部分文字列と左から右へ照合することで進行される。型式の直後にリテラル文字列が続く場合、推論元文字列内でそのリテラル文字列が最初に現れる位置まで、推論元文字列から0個以上の文字を推論することで照合される。 ただし、「型式の直後に別の型式が続く場合、推論元文字列から単一の文字を推論することで照合される。」

引用元: https://stackoverflow.com/questions/76112923/how-do-i-correctly-use-multiple-instances-of-the-infer-keyword-with-template-lit

つまり、

type ContainsUnderFirst<S> =
  S extends `${infer First}_${string}` ? First : never;
type A = ContainsUnderFirst<"_">; // A = ""
type A = ContainsUnderFirst<"aaa_">; // A = "aaa"
type A = ContainsUnderFirst<"_aaa">; // A = ""

みたいな感じになるということです。

1-4. 結合関数を作って1つの文字リテラルを生み出す!

文字リテラルの文字を抽出で来たら結合をして1つの文字リテラルとして生み出しましょう! つまりjoinを作るというわけですね。

type TupleJoin<Tuple> =
  Tuple extends [infer Str extends string]
    ? Str
    : Tuple extends [
        infer Char extends string,
        ...infer Remain
    ]
      ? `${Char}${TupleJoin<Remain>}`
      : never;

これも再帰的にTupleを取り出していって1文字づつ文字リテラルにして言っているだけです。

1. ["a", "b", "c"] // 3. a + bc
2. ["b", "c"] // 2. b + c
3. ["c"] // 1. c

これもTupleとinferで推論をしていて、Tupleでは、通常の配列同様にスプレッド構文が使用できちゃいます。 なので、[infer T, ...infer U]みたいにすると、Tには最初の要素が、Uにはそれ以外の要素が入るような推論をしてくれます。 なのでこのやり方で再帰的に行っていくと最初の要素がどんどんなくなっていくような挙動になるので、要素がなくなったときに文字列を左から埋めていくと、まるでjoinのような挙動を再現できちゃうんですね。

1-5 最後にlengthを錬成する!

さあここまで来たらあとは作った型を元に作るだけ!

type Length =
  TupleJoin<[
    GetLastChar<ToString<null>>,
    GetLastChar<ToString<true>>,
    GetFirstChar<ToString<null>>,
    GetFirstChar<PickKey<typeof globalThis, typeof globalThis>>,
    GetFirstChar<ToString<true>>,
    GetLastChar<PickKey<typeof globalThis, Math>>,
  ]>;

nullの「l」、trueの「e」、nullの「n」、globalThisの「g」、trueの「t」、Mathの「h」 これらを掛け合わせると...念願の"length"が完成!!!!!!

なげぇ!あとめっちゃ無理やり!!!

part2へ...

結構長かったですね。 でも大丈夫です!ここまでのことが理解できていればあとはもう同じような要素を組み合わせていくだけなので、実質ウィニングランみたいな感じです。

次は「「Type」ScriptでHelloWorldをしてみた Part2」でお会いしましょう。

Claudeで設計、Codexが実装:MCPでつなぐAI開発環境

こんにちは!まっちゃんです! 👋

皆さん、Claude Codeは活用できていますか?

私は、現状でもSerena MCPを導入するなどして、結構活用できているように感じています!

ですが、AIを活用する際に、もう一歩前に進む方法を @spookies_yuushi_suzuki と考えてみたので、共有したいと思います!

Claude Code を使って Codex を使ってみる

要するに、どっちもの良いとこ取りをしたいわけですね👀

モデルの得意不得意に決定的な違いがある

現状、Claude Code の使用しているClaude Sonnet 4.5 と、Codexの使用している GPT-5 Codex には、決定的な違いがあるのです

何かというとコンテキストウィンドウの使い方です

どのような長所・短所があるかを図にしてみました👀

モデル名 開発元 コンテキストウィンドウ(入力) 長所(得意な点) 短所(課題・注意点)
Claude Sonnet 4.5 Anthropic 約200K トークン(20万トークン) ・「コンテキストウィンドウ」という制約を超え、タスク全体を通しての流れを把握できる(らしい)
・大量のコードや仕様書を一度に読ませても、理解が破綻しにくい。
・レスポンスは速いが、生成コードがやや大雑把な場合がある。
・数回のプロンプト修正を経ないと、期待通りの実装に至らないことが多い。
GPT-5 Codex OpenAI 約272K トークン(約28万トークン)
参考
・既存コードを正確に読み取り、文脈を踏まえたうえで高精度に実装可能。
・プロジェクト全体の整合性を保ちつつ、動作するコードを高確率で出力できる。
・セッションが長くなると古い履歴を圧縮し、文脈の一部が欠落することがある。
・長期の調査・開発セッションでは、途中経過や思考履歴が失われやすい。
・レスポンスまでが長い。

*1

コンテキストウィンドウが小さいのに、どうして Claude Sonnet 4.5 は大きなコンテキストを扱えるの?というのはここには書ききれないので、 https://www.anthropic.com/news/context-management を参照することとします。

簡単に書くと、不要になったコンテキストを賢く削除したり、重要な情報はメモリに保存することによっていい感じにしているらしいです。

どう生かすの?

Claudeが全体的な設計を、Codexが実装を担当するように分けることで、Claudeの理解力とCodexのコーディング頭脳を両方活かせる(はず)です!

要するに、Claudeは設計者、Codexは実装者として使い分けるのが理想だよね。という感じです 👀

環境構築の前提

まず、CodexとClaude Codeが入っているかつログインされている必要があります。

まだ入っていない方は、入れましょう。

注意

Codex は ChatGPT の Plus 以上のプランが必要です。

https://openai.com/ja-JP/codex/

Claude Code は Pro でも使えなくはないですが、リミットがずいぶん厳しいと思います。

Proで使ってみて、よかったらMax x5 にあげるのがおすすめです

https://docs.claude.com/ja/docs/claude-code/quickstart

実際にやってみた

実際に、Claude Code でCodexを使用できるようにしてみましょう 👀

そのためには、Claude Code にMCP経由でCodexを使えるようにしてあげる必要があります。

下記のコマンドをするといい感じになります。

claude mcp add codex-mcp codex mcp-server

コマンドの構文を解説します。

claude mcp add [名前] [コマンド] [引数]

のような感じです。

なので上のコマンドは、

codex というコマンドに mcp-server という引数をつけたもの( codex mcp-server というコマンド )を、 codex-mcp という名前のMCPとして登録する。のようになりますね。

尚、古い記事だと codex mcp というコマンドで追加するような記事がありますが、もう既に古い構文となっているようです。*2 *3 *4

この状態で、あとはClaude CodeでCodexを使って!と言うだけ!

ではありません!

このままでは、Claude Code 自身がどうCodexを生かせばいいのか知らないからですね!

Claude CodeがCodexを気持ちよく動かすためには??

使うためにはプロンプトをいい感じにしてあげる必要があります。

これを理解するためには、Codexのオプションを理解する必要があります。

approval-policy

簡単に言うと、 Codex自身が、Toolを使う際に許可を求めるかどうか なのですが、このオプションには、"never" | "on-request" | "on-failure" | "untrusted"のどれらが入ります。

それぞれ、

  • never (推奨)
    • どのツールも勝手に使っていい(許可を求めなくていい)
  • on-request
    • Codex 自身が判断して、許可を求める
  • on-failure
    • コマンドが失敗した時に、許可を求める(?)
  • untrusted
    • 信用済みでないコマンドは実行前に許可を求める

のような挙動になっているようです。

その上で、Codexのツールの使用をMCP経由でapprovalするという事に、恐らくClaude Codeが対応していないようなので、on-requestにすると、詰みます。

これであなたもFull Stuck Enginnerです。

sandbox

こちらのオプションは、下のような3つがあります。

  • read-only
    • そのまま。読み取り専用。
  • workspace-write (一番推奨)
    • ワークスペースの中のファイルは編集可能
  • danger-full-access
    • 全部にアクセスできる。破壊し放題

read-onlyをつけられると実作業ができません。

また、Codexくんが動いている間は、進捗が不透明で結構不安になるので、タスクを振る粒度は細かい方が良いでしょう。(多分?)

なので、上記3点を踏まえ、下の方なプロンプトをClaude.mdに突っ込んでおくと良いでしょう。(例なので、いい感じにしてみてください!)

実際の実装に差し掛かる際には、Codex MCPを使用してください。
オプションとして、
workspace には、 workspace-write を
approval-policy には、 never を
つけて、Codex MCPを使用してください。
また、Codexに振るタスクは細分化して、細かく投げて、進捗を追いやすくしてください。

このようにしてあげると、Claude Code は動きやすくなります!

思いついてから突発的に書いているので、感想などはありません!

もっと使ってから、感想だけ追記しておきます!

最後に

いかがでしたか!

これらの考えが参考になれば幸いです!

進化していくAI、追うのも大変ですが、その中で最適解を見つけるために紆余曲折していかなければならないのも、また楽しいですね 👀

また、もしかしたら、Codexの中でCodex MCPを使用することでSubAgents的な動きをできるのでは...?のような考えもあるので、そのようなこともやっていきたいですね 👀

もし、ここが間違ってる!などがあれば是非ご指摘ください 🙏

最後まで読んでいただきありがとうございました!

参照 & 参考

Codexのオプション周り、参考にしました! ありがとうございます 🙏

-> https://zenn.dev/hokuto_tech/articles/97fa88f7805a23

以下ドキュメントなど

弊社社内VPNをSoftEtherからWireguardに置き換えた話。

こんにちは。スプーキーズ東京オフィス窓口担当、鈴木です。 気づけば、この記事を書いてから半年以上が経過しましたね。 labs.spookies.co.jp いや、違う。 まだ一年経ってないはずなんですよ。 なのにAIコーディングツールの進歩が速すぎる。 前回まっちゃんがClaudeCodeについて書いてくれましたが、このClaudeCodeだってこの記事書いたときにはまだ名前すら出ていませんでしたからね!? ついていけねぇって...... なので、今回はAIに関する記事は書きません。 どうせ一か月後にはレガシーな情報になっていることが目に見えているので。 今回は弊社VPNをWireguardで再構築した話を書きます。

元の構成

もともと、弊社のVPNはけしからんで有名なSoftEtherが軸でした。 京都オフィスにおいてある物理マシンにSoftEtherサーバーを載せて運用していました。 ただ京都オフィスのネットワークに接続する分には問題なかったのですが、最近いろいろと不自由が出てきてしまい.......

不自由1:拠点間接続が激重

これは直接自分が試したことではないので確かなことは言えないのですが、まだオフィスが渋谷にあったころ、どうやらSoftEtherで拠点間VPNを東京京都間で設置してみたことがあったそうな。結果、重すぎて使い物にならなかったらしい。 まぁみんなで京都のネットワーク使えばいいよねって話で落ち着いたらしいんですが、 ここにきて京都と東京のそれぞれのローカルネットワークをつなげたいという要望が再燃。 VPNのリプレイスも含めて、再度方法を検討しました。

不自由2:L2TP/IPSecを使わざるを得ないケースでNATの扱いが厄介すぎる

MacOSの場合SoftEtherを直接使えないので、L2TP/IPSecを介して接続するわけですが、これが厄介。 利用するIPv4ネットワークがIPIPだとポートフォワーディングができず、VPNが使えないという問題が生じていました。 これのせいで一部のメンバーはテザリングで頑張ってリモートワークをしていたそうです。

まずWireguardとは?

ひとことで言うと、「シンプル・高速・セキュア」を突き詰めた、イマドキのVPNプロトコルです。 コードベースがとにかく小さい: 他のVPN実装(OpenVPNやIPSec)に比べて、コードの行数が圧倒的に少ない。コードは少なければ少ないほど良いとされています。 設定がシンプル: 基本的には、SSHの鍵交換のように、お互いの公開鍵を交換して設定ファイルに書き込むだけ。SoftEtherやIPSecのような複雑な設定項目に頭を悩ませる必要がありません。 めちゃくちゃ速い: WireGuardはLinuxカーネル内で直接動作するため、パフォーマンスが非常に高い。これが拠点間接続の速度問題を解決してくれるんじゃないかと期待しました。 NAT越えが得意: WireGuardは特定のポートフォワーディングを必要とせず、クライアント側からサーバーにパケットを一度送信できれば、あとはよしなに通信を維持してくれます。これにより、厄介なNAT問題から解放される見込みが立ちました。 これはもう、やるしかないだろうと。

新しい構成

検討の結果、以下のような構成に落ち着きました。 ※なお、IPは例。そりゃ本物のIPマップなんて公開できるわけない。

  1. 京都オフィスにwg-easyサーバーを構築 VPNのハブとなるサーバーは、京都オフィスの物理マシン上に構築しました。ユーザー管理のしやすさを考慮し、wg-easyを導入してWeb UIから操作できるようにしています。

  2. 東京オフィスにネットワークゲートウェイを設置 東京オフィスには、破棄直前のノートPCを引っ張り出しubuntuに乗せ換えて、ネットワークの出入り口となるゲートウェイとして設置。このゲートウェイが京都のwg-easyサーバーに常時接続することで、東京のネットワーク全体がVPNに参加する形です。

  3. 各メンバーは京都のサーバーを介してアクセス リモートメンバーや東京オフィスのメンバーは、一度京都のwg-easyサーバーに接続します。そこを経由して、京都のローカルネットワークはもちろん、東京のネットワークゲートウェイを通じて東京のローカルネットワークにもアクセスできる、という仕組みです。

やってみてどうだったか?

解決1: 拠点間接続が爆速に

以前は「使い物にならなかった」という拠点間接続ですが、今ではVPN経由で東京オフィスのリソース(今はIPカメラ)にアクセスしても、全くストレスを感じません。 ローカルネットワークと同じ…とまでは言いませんが、体感速度は劇的に向上しました。 これで、どちらのオフィスにいても、シームレスに必要な情報にアクセスできる環境が整いました。

解決2: NAT問題を完全に克服

あれだけ苦しめられたNAT問題は、嘘のように解消されました。自宅のネットワーク環境が原因でテザリングを余儀なくされていたメンバーも、今では何事もなかったかのように自宅のWi-FiからVPNに接続しています。クライアントソフトも軽量で、PCを起動したらほぼ自動で接続が完了しているので、「VPNに繋いでる感」がほとんどありません。これは本当に嬉しい誤算でした。

ハマった点・今後の課題

もちろん、すべてが順風満帆だったわけではありません。

ユーザー管理については、WireGuardのシンプルな仕組みのままだと、ユーザーが増えるたびに手動で設定ファイルを編集する必要があり大変だろうと予想していました。 そこで今回は当初から「wg-easy」というWeb UIツールを導入し、ユーザーの追加・削除がブラウザ上から簡単に行えるようにしました。 が、こいつが曲者で、ハブアンドスポーク以外の構成を想定していないため、Wireguardのメリットである構成の自由度がなくなってしまったのです。

今回我々は構成を工夫することで回避できましたが、ユースケース次第、特に拠点ごとにユーザー管理をしたいケースなどはWG-Easy以外の管理方法を検討する必要があるでしょう。

まとめ

というわけで、今回は弊社のVPNをSoftEtherからWireGuardにリプレイスした話を書きました。 AIの世界ほどのドッグイヤーではありませんが、インフラ技術も着実に進化しているんだなと改めて実感しましたね。 もし同じような「不自由」を抱えている方がいたら、WireGuardへのリプレイス、本当におすすめですよ。 これでまた一つ、弊社のリモートワーク環境が快適になりました。 さて、次はどの部分を改善しようかな。 それでは、また。

Full Weak Engineer CTF 2025参加しました!

こんにちは!Spookiesに入ってもう少しで3ヶ月、こまさんです!!
CTFは、入社する3年ぐらい前に一度参加してからずっとノータッチだったんですが、SpookiesにCTF部がある!という事で最近はボチボチ頑張ってます💪

という事で、今回はSpookiesのCTFチームspookiesFull Weak Engineer CTF 2025に参加してきました!

まずは結果から!

今回は一緒に参加してた方がめちゃくちゃ頑張ってくれて17位でした! 結構すごいのでは?!

私が解いた問題は4問だけですが、ちょっとその中から面白かったのをピックアップして話していきたいと思います!

まずはみんなの活躍を紹介~

  • Nemoola
    • Poison Apple (100pts./444solves/Misc)
    • AED (127pts./232solves/Web)
    • regex-auth (100pts./450solves/Web)
  • Huzuni
    • Welcome (100pts./522solves/Welcome)
    • strings jacking (100pts./447solves/Rev)
    • I_HATE_DEBUGGING (395pts./25solves/Rev)
    • Mystery Zone (227pts./97solves/Rev)
    • No need Logical Thinking (132pts./219solves/Rev)
    • NativeKotlian (497pts./2solves/Rev)
    • Pwn Me Baby (153pts./178solves/Pwn)
    • Flagcraft (277pts./67solves/Misc)
    • GeoGuessr1 (100pts./404solves/OSINT)
    • GeoGuessr2 (100pts./320solves/OSINT)
    • GeoGuessr3 (100pts./318solves/OSINT)
    • datamosh (106pts./294solves/OSINT)
    • RSA Phone Tree (124pts./240solves/OSINT)
    • GeoGuessr4 (133pts./217solves/OSINT)
    • QR (211pts./110solves/OSINT)
    • Osaka Expo Pavilion Quiz! (256pts./78solves/OSINT)
    • EXIT (147pts./189solves/OSINT)
    • MYAKUMYAKU TOWER (260pts./76solves/OSINT)
    • git predator (277pts./67solves/OSINT)
  • Komasan_ (私じゃ!)
    • baby-crypto (100pts./563solves/Crypto)
    • base🚀 (101pts./316solves/Crypto)
    • unixor (324pts./47solves/Crypto)
    • A (459pts./10solves/Rev)
  • mattyatea
    • Adversarial Login (230pts./95solves/Misc)

こうやって見るとそこまで活躍はできなかったですね、、😅 まぁ反省会は置いといて、、、

この中の「A」という問題がなかなかインパクトあって、面白かったので紹介してみますね!

まずは見てみよう

まぁまず添付ファイルが「main.py」と、、Pythonですね!(Python大好き人間なので嬉しい!!) とりあえずダウンロードしてコードを見てみましょう。

もういかつい

うひゃ~~、なんかとんでもないコードが出てきてしまいましたね。。(画像かなりちっちゃくて見づらいかも、、😨)

という事で、パッと見た感じ、Pythonの特殊メソッドを使って、演算子オーバーロードがたくさんされているみたいですね。。 で、とりあえず実行してみたのですが、流石にどこか弄られていて動きませんでした。(動いたらまだデバッガとにらめっこしてもよかったのですが、、)

じゃあ気合で難読化を解除していきます。

気合で解読

まず、最初はclassが2つ定義されていて、片方はメタクラスA、もう片方はAを継承したクラスA、という構造になっていますね。

演算子オーバーロードだらけ

まずできそうなことは、base64でエンコードされた文字列がたくさんあるので、ここをデコードしてみましょうか。

なんか作者からのメッセージがある。。(確かにSHA-256はだるいなぁ)

なんか、メソッド名とかクラス名とか色々ありますね。。ここから取ってきてメソッドとか実行しそうな感じ。

ここからは、まずクラスAの中身をほぐしていきます。。もう気合しかありませんね。

⌛⌛⌛作業中⌛⌛⌛

とまぁ、そんな感じで、各メソッドを気合で解きほぐしたらこんな感じになりました。

3分クッキングを彷彿とさせる差し替え術

どうやら、簡易的なスタックマシンが作られているみたいですね。 (なおスタックにはリストとかのオブジェクトも突っ込める現代仕様)

このスタックマシン的構造でコードを実行するようだ。。

そんで本題の下のコードの処理も展開していくと、、、

ダイエット大成功

めっちゃシンプルになりましたね!!!(苦笑)

本当に初動のコードが嘘みたい。

それでコードを読むと、、

「要はflagを入力して、その文字列の文字毎のコードの総和をシードとして、ランダム値を生成してそれと入力をxorしたものがコード中にあるlistに合致すれば正しいフラグだよ」

という感じですね。

あと49文字だという事も分かるので、seedの取りうる範囲での総当たりで解読できそうですね。

ここまでくれば、もうちゃちゃっと行けるかな(この問題の本題はRevであってCryptoじゃないのでね)

あー楽しかった🚩

Javaに演算子オーバーロード、、、、、だめです!!!!もっと難化しちゃう。

感想

今回はちょっとインパクトがあったRev問を一つWriteup的に書いてみました。 (といっても実質的に作業が地味すぎるからほぼ差し替えですが。。)

楽しかった!!この調子で、すこしずつCTF部も盛り上げていきたい!!

あとPwn問を練習したいなぁと思っていたり。。 日々精進ですね。頑張ります。

Claudeの可能性を探ってみた

この記事は人間が書いています

こんにちは。

まっちゃんです 👋

現在、SpookiesではAIの導入を進めています。

導入を進めているAIはAnthropicという会社が開発している、ClaudeというAIです。

そのAIをどのように活用していけば良いかを書いていきたいと思います。

開発者が執筆しているので、開発者的な目線での解説が多いかと思いますが、ご了承ください 🙏

Claude、何ができるんだ

現状、AIには得意不得意がありますが、Claudeくんは、プログラミングが得意です。

実際に説明させてみましょう。

Claudeって何がするのが得意なの?

Claudeは以下のような分野が得意です:

文章作成・分析
長文の要約、技術文書の作成、メールの下書き、創作活動など、様々な文章タスクを高品質でこなせます。複雑な内容も分かりやすく説明するのが得意です。

プログラミング・技術サポート
コードの作成、デバッグ、リファクタリング、技術的な質問への回答など。多くのプログラミング言語に対応しており、アーキテクチャの設計相談なども可能です。

データ分析・可視化
CSVやExcelファイルの分析、グラフ作成、統計的な洞察の提供など。アップロードされたデータから意味のある情報を抽出できます。

問題解決・論理的思考
複雑な問題を段階的に分解し、論理的なアプローチで解決策を提案します。ブレインストーミングや意思決定のサポートも得意です。

学習・教育サポート
概念の説明、例題作成、学習計画の立案など。難しいトピックも理解しやすく噛み砕いて説明できます。

クリエイティブな作業
アイデア出し、ストーリー創作、デザインコンセプトの提案など、創造的な思考をサポートします。

何か特定の作業でお手伝いできることはありますか?

めちゃくちゃ長いレスポンスが帰ってきたので、要約させます

要約してください

Claudeが得意なことを簡潔にまとめると: - 文章・コミュニケーション - 要約、執筆、説明が得意 - プログラミング - コード作成・デバッグ・技術相談 - データ分析 - ファイル分析・グラフ作成・洞察提供 - 問題解決 - 複雑な課題を論理的に分解・解決 - 学習サポート - 概念説明・教育コンテンツ作成 - クリエイティブ - アイデア出し・創作活動

つまり、文章から技術まで幅広い知的作業をサポートできる汎用的なAIアシスタントです。

幅広いらしいです(ほんまか)

というのはさておき、実際に何ができるかみてみましょう。

使うときに大切なこと

なるべく、タスク化をさせて、そのタスクの詳細などに、やりたいことを詳細に書いてもらいましょう。

それを私たち人間が精査して、修正して、Claude CodeなどのAIに取り掛かってもらうことで、「思ってたのと違うが??」が発生しにくくなります。

また、コーディングのフローで言うと、

  1. タスクが切られる
  2. タスクを作業者が精査する
  3. 精査した情報を元に、作業する
  4. 作業者がPRを作成する
  5. レビューしてもらう
  6. 作業者がレビューから修正、返答などを行う
  7. マージする

のようなフローですが、その中で作業者の部分がClaude Codeになって、レビュー者が人間になるようなイメージが一番わかりやすいです。

なので、Claude Codeなどが生成したコードをレビューするのが我々の役目です。

それでレビューをし、おかしな点などをどんどんプロンプトに入れて、修正してもらいます。

また、その時にルールとして定義させて、起こらないようにするなどのことも必要です。

プログラミング

マジで、めちゃくちゃ得意です。

後述するClaude Codeくんがとてもすごいです。

実際にCloudflareのテンプレートをもとに、MCPサーバーテンプレートを作成したり、GitHub連携のMCPサーバーなどもClaude Codeに作らせています。

でも、難しいんでしょう?????????

ちなみに、そんなことはないです。

人間と同じで、なるべく具体的に指示をしてあげることが重要です。

基本的には、

  • 何を使って
  • 何をできる
  • 何を作る

  • 何を使って
  • 何を処理する

などのプロンプトで十分です。

様々な場面があると思うので何個か、私がプロンプトをどの場面にどうやって組んでいるかを解説していきます。

新規開発

Claude Codeで、

  • どうして
  • 何を使って
  • 何をできる
  • 何を作る
  • 作る時のルール

のように指示します。 また、この際に仕様書的な感じでたくさん詳細に入力してあげると良いでしょう。

これは私が新しいアプリケーションを作成しようとした際のプロンプトの例です。

# きっかけ
AIを使う際に、ルールを使いまわしたい時があります。
その時に使用できるような、CLIツールのBackendを開発してください。
Cloudflare Workers を使用して、Cloudflare D1にメタデータ、R2に実際のMarkdownを保存する様なサーバを作ってください。

# 技術仕様
- サーバレス: Cloudflare Workers
- DB: Cloudflare D1
- Storage: Cloudflare R2
- API: Restful API
- Deploy: GitHub Actions
- Authenticate Method: JWT

# 要件
- なんかたくさん

# 開発のルール
- 複数人で開発するので、詳細にコーディングルールを明文化すると良いでしょう。
- 最初はClickUpにタスクを作って、詳細にやりたいことのタイトルの最初に[Task]をつけて、タスク化してください。
- そのタスクの詳細には、やることをチェックリストで保存してください。
- また、設計などで困ったこともClickUpで タイトルの最初に[QA]をつけて、起票してください。
- 何かしらのタスクに手をつけたり、終わったりした際には、状態の更新を忘れないでください。
- また、指示をされたがClickUpに存在しないタスクの場合は、タスクを新規作成するべきです。
- タスク周りの処理は絶対に怠らないでください。

実際は、新規開発の場合、もっとコンテキストを与えてあげると良いです。

既存のものに追加の機能をつけるとか

Claude Codeで、下のような感じでプロンプトを構築しています。 - 何をできる - 何を作る - 作る時のルール

例えば、前あった社内のツールの改善タスクでは、

# 目的
タスクの概要

# 具体例
何が、どのようになるべきか

のようにタスクを投げます。

次に、動作確認をして、動かなかったりコンパイルでエラーが発生した際には、

エラー内容 のように出て動かないので修正して

のようにぶん投げます。

最終的に動作確認ができたら、

PRを作りたいので、下記のテンプレートにまとめてください。

# 概要

# できること

# テスト内容
- [ ] テストの内容
- [ ] テストの内容1
...

と指示し、PRの概要を作らせて、PRを作ります。

事務作業とか

あんまり例が思いつかないですが、例として、領収書をまとめさせてみましょう。

├── 未処理/
│   ├── 未処理の領収書.pdf
│   ├── 未処理の領収書2.pdf
│   └── 未処理の領収書.png
└── 処理済み/

のようなファイルがあるとします。

Claude Codeで、次のように指示します。

未処理のファイルに入っているpdfやpngファイルを読んで、YYYY/MM/DD-事業者名-¥値段 のような形で、処理済みに移動させてください

そうするとおそらくいい感じになります。(検証できていない)

MCPと組み合わせる

新しい単語が出てきたと思いますが、こちらは別途、解説する予定です!

AIはMCPと組み合わせることで、何倍も強くなります。

例えば、GitHubやClickUpのMCPを使えば、GitHubのIssueやClickUpのチケットから、作業内容を作成して、ClickUpに具体的なチケットに起こして、それをClaude Codeに作業させることができたりします。

その作業内容をEsaにまとめるようにさせれば、作業ログも取れますね。