IDA Proで独自VM問を読む
この記事は、CTF Advent Calendar 2021の4日目です。
CTFで出題されるreversingの問題のひとつに、独自VMと呼ばれる、独自に実装されたVirtual Machineの上で動くバイトコード(プログラム)の解析を行うものがあります。
この問題ではVM自体のバイナリを読み、どのような命令があるのか、どのような挙動なのかを把握するのはもちろん、最終的にはそのVM上で動くバイトコード自体を読み解く必要があり、かなり根気がいります。
VMのバイナリはいつも通りIDA Proで解析すればよいのですが、その後のバイトコードの解析にはもちろんIDA Proが使えず、簡易的なディスアセンブラを実装し、テキストエディタでメモを書きながら読んでいく必要があります。関数呼び出しや分岐があればあるほど読みにくくなっていき、解析にかなりの時間を費すことになります。
そこでIDA Proのプラグインを自分で書いて、独自VM問のバイトコードをIDA Proで読めるようにしてしまおうというのがこの記事での本題です。
IDA Proのprocessor moduleを書く
IDA Proではprocessor moduleを書くことで独自のアーキテクチャを定義し読み込むことができます。
IDA Pro本体に同梱されているprocessor moduleのほとんどはC++で実装されていますが、CTFで対象となるのは小規模なバイナリなので書きやすさを優先して今回はPythonで実装しました。 独自のprocessor moduleを書く際のテンプレートや、Pythonで書かれたprocessor moduleもいくつか同梱されているので、それらを参考に書いていきます。
- IDA SDKの
module/script/proctemplate.py
を元に書いていくmodule/script/ebc.py
,module/script/msp430.py
がPythonで書かれたprocessor module- IDA SDKのドキュメントはこれ https://hex-rays.com/products/ida/support/sdkdoc/index.html
- idabinの
python/3
以下にidapythonのモジュールがあるので補完が効くようにIDEに読み込ませておく
- 最後にスクリプトをidabinの
procs
以下に置くことで、ファイル読み込み時にprocessor typeとして選択できる
次に過去に出題された独自VM問のために作ったprocessor moduleを紹介します。
例題1: baby-a-fallen-lap-ray - DEFCON 2021 Quals
よくわからないマシン(エミュレータ)上で動くVM上で動くバイトコードのreversingです。解析自体はかなりつらいです。
作ったprocessor module: https://github.com/akiym/ida-ctf-vm-chall-reversing/blob/0a499f8427eb09241f7c4b861314aa87c17e6230/procs/fallen-lap-ray.py
- 命令の定義
- ジャンプする命令は
CF_JUMP
をつけておき、ev_emu_insn
で判別に使う - 実際に使っているのは
CF_JUMP
とCF_STOP
だけなので、使っていないなら適当でも動くはず - https://github.com/akiym/ida-ctf-vm-chall-reversing/blob/0a499f8427eb09241f7c4b861314aa87c17e6230/procs/fallen-lap-ray.py#L70-L101
- ジャンプする命令は
ev_ana_insn
ev_
系の関数はIDA自体から呼ばれるハンドラのようなもので、これは実際にバイトコードを読んで対応する命令を定義していくところ- https://github.com/akiym/ida-ctf-vm-chall-reversing/blob/0a499f8427eb09241f7c4b861314aa87c17e6230/procs/fallen-lap-ray.py#L505-L598
- 人間が読みやすくしたいのでaliasに変換する
- スタックの操作はpush, popのような命令で読むことに慣れているのでそうしておく
- https://github.com/akiym/ida-ctf-vm-chall-reversing/blob/0a499f8427eb09241f7c4b861314aa87c17e6230/procs/fallen-lap-ray.py#L476-L503
ev_emu_insn
- 命令から対応する操作や意味を定義していくところ
- jmp命令で飛ぶ先やimm命令で読もうとしているデータへのcross referenceを追加して、まさにgraph viewで見えるような命令同士の繋がりを定義する
- https://github.com/akiym/ida-ctf-vm-chall-reversing/blob/0a499f8427eb09241f7c4b861314aa87c17e6230/procs/fallen-lap-ray.py#L361-L433
- スタックポインタのトレース
- push, pop命令や$sp自体への加算をトレースして、Options -> General -> Display dissassembly line parts (Stack pointer)から表示切り替えできるスタックポインタに反映させる
- https://github.com/akiym/ida-ctf-vm-chall-reversing/blob/0a499f8427eb09241f7c4b861314aa87c17e6230/procs/fallen-lap-ray.py#L328-L359
実のところ、IDA Proでバイトコードを読むというアイディアは以下のwriteupからいただきました(元記事ではBinary Ninjaを使っています)。ありがとうございます。
例題2: EmojiVM - HITCON CTF 2019
この問題はスタックマシン型のVMなのですが、自分の中で解析方法が定まっておらずprocessor moduleでスタックの状態をエミュレーションしながら、その結果をコメントに追記していく形にしました。あまりIDA Proでの解析の恩恵を得られなかった例です。
作ったprocessor module: https://github.com/akiym/ida-ctf-vm-chall-reversing/blob/0a499f8427eb09241f7c4b861314aa87c17e6230/procs/emojivm.py
まとめ
IDA Proを使って独自VM問のバイトコードの解析ができるようprocessor moduleを書いてみました。今後出題される問題でも今回書いたスクリプトを少し書き換えれば応用可能なので、また使う機会があるかもしれません。
実際のところ、IDA Proで読めたからといってそこで終わりではなく、ここからまた時間をかけて人間が読む作業は残っています。 2020 Plug-In Contest – Hex Rays にbfというbrainfuckをHex-Rays decompilerでデコンパイルするというプラグインがあったので、解析補助のためにデコンパイラを実装できなくはないのかもしれませんが、1つのVM問に対する実装量がかなり多くなるはずで、現実的には人間が読むほうが早いということになりそうではあります。
今後も問題を解く上で書いたprocessor moduleは以下のリポジトリに追加していく予定です。面白かった独自VM問の過去問がありましたらIDA Proで読もうと思いますので是非 @akiym まで教えてください。
PerlでもgRPCで通信したい
まずはじめに、2021/2時点でgRPCがサポートされている言語にはPerlは含まれていなく、公式にはサポートされていません。 現時点でと言ったものの将来的にもサポートされることがないだろうことからPerlでgRPCを扱うのは茨の道といえるでしょう。
おとなしくgRPC transcodingしてHTTP REST APIで叩きましょう、というのがほぼ答えなのですがCPANに公開されているライブラリを使ってどこまでできるのかを検証するのがこの記事の目的です。
題材
gRPCで通信といっても、サーバとクライアントのどちらをPerlで実装するかという話になりますが、今回実装するのはクライアントです。 他の言語で書かれたマイクロサービスからPerlと通信することを想定して、手軽な例としてGAPIC Showcaseのサーバと通信することにしてみます。
google.showcase.v1beta1
packageにはいくつかのserviceが提供されていますが、その中でもEcho
serviceの各メソッドを呼び出してみることを題材とします。
protoファイルに定義されたスキーマには、単純にリクエストを投げてレスポンスが返ってくるだけのEcho
メソッドやサーバストリーミング、クライアントストリーミング、双方向ストリーミングなど形式で通信を行うメソッドが用意されています。
ちなみにIdentity
serviceなどでも試したかったのですがproto3のoptional fieldが使われているため見送りました。
PerlからProtocol Buffersを扱う
protoファイルを元にメッセージのエンコード/デコードを行うためにGoogle::ProtocolBuffer::Dynamicを使います。 ほかにもモジュールは世に存在しているのですが、proto2にしか対応していない、メンテナンスされていないことから選択肢としては実質このモジュールしかありません。
Google::ProtocolBuffer::Dynamicはその名前の通り、スタブコードを事前に生成しておくのではなく、protoファイルを読み込んで動的にインタフェースを生成します。
gRPCでクライアント通信をする場合はオプションを渡すことでGrpc::XSが内部で使われるようになります。Grpc::XSはCPANTSの結果を見るとMETA.ymlが存在しなくDevel::CheckLibの依存が漏れていたりなどと少し不安ではありますがGoogle::ProtocolBuffers::Dynamicからはこのモジュールを使うしかありません。
試してみる
PerlからgRPCで通信はできそうということがわかったので、実際にコードを書いて試してみます。今回書いたコードの全体は以下のリポジトリで公開しています。
まずはprotocコマンドを使ってスタブコードを生成します。このスタブコードというのはprotoファイルに定義されたメッセージの生成やメソッドの呼び出しを行えるようにするためのクライアント用に生成されたコードです。
以下のコマンドを実行するとGrpcSandbox::PB
というPerlのパッケージが作られます。
生成されたコードにはシリアライズされたデータとgRPCとPerlのパッケージの紐付けが含まれており、実際にserviceを呼び出す際にはGrpcSandbox::PB::Google::Showcase::V1beta1::Echo
パッケージを参照するといった形で行ないます。
% protoc \ -Ithird_party/gapic-showcase/schema/api-common-protos \ -Ithird_party/gapic-showcase/schema \ --perl-gpd_out=package=GrpcSandbox.PB:lib \ --perl-gpd_opt=client_services=grpc_xs \ third_party/gapic-showcase/schema/google/showcase/v1beta1/echo.proto \ $(find third_party/gapic-showcase/schema/api-common-protos/google -name '*.proto') \ $(find /usr/local/include/google -name '*.proto')
ここで注意する点としては、GAPIC Showcaseが依存しているapi-common-protosとgoogle.protobuf
packageのprotoファイルも読み込む必要があることです。必要に応じてprotoファイルのinclude pathも指定します。
余談ですがprotocの挙動としては--perl-gpd_xxx
というオプションが渡されることでGoogle::ProtocolBuffer::Dynamicの提供するproto-gen-perl-gpd
というコマンドが呼ばれるようになります。
このコマンド同士のやり取りにもProtocol Buffersが使われており、オプションやprotoファイルの一覧がCodeGeneratorRequestとして渡されていたりします。
Echoメソッドの実装
クライアントライブラリを提供するという形でgRPCで通信するメソッドを実装していきます。 適宜protoファイルを見ながら読んでもらえると理解しやすいと思います。
まずは以下のコードのようにしてEcho
serviceへのコネクションを作成します。
コード中にでてくる $self->service
はこれを指します。
my $service = GrpcSandbox::PB::Google::Showcase::V1beta1::Echo->new( 'gapic-showcase:7469', credentials => Grpc::XS::ChannelCredentials::createInsecure(), );
serviceにあるメソッドの呼び出しは->Echo
のように同じ名前で呼び出す形に対応します。
google.showcase.v1beta1
packageのEchoRequest
に対応するパッケージはGrpcSandbox::PB::Google::Showcase::V1beta1::EchoRequest
です。
Echoメソッドは単一(Unary)リクエストなので、呼び出し後は->wait
を使ってEchoResponse
に対応するオブジェクトを取得します。メッセージのフィールドの値はget_
というprefixをつけて取り出すことができます。
sub echo { my ($self, $content) = @_; my $req = GrpcSandbox::PB::Google::Showcase::V1beta1::EchoRequest->new({ content => $content, }); my $call = $self->service->Echo(argument => $req); my $res = $call->wait; return $res->get_content; }
Expand, Collectメソッドの実装
Expandメソッドは複数のレスポンスを受け取り(サーバストリーミング)、Collectメソッドは複数のリクエストを送ります(クライアントストリーミング)。
sub expand { my ($self, $content) = @_; my $req = GrpcSandbox::PB::Google::Showcase::V1beta1::ExpandRequest->new({ content => $content, }); my $call = $self->service->Expand(argument => $req); my @res = $call->responses; return [map { $_->get_content } @res]; } sub collect { my ($self, @contents) = @_; my $call = $self->service->Collect(); for my $content (@contents) { my $req = GrpcSandbox::PB::Google::Showcase::V1beta1::EchoRequest->new({ content => $content, }); $call->write($req); } my $res = $call->wait; return $res->get_content; }
Chatメソッドの実装
Chatメソッドは双方向ストリーミングを行ないます。1つずつリクエストを送ってはレスポンスを受け取るという形にしてみました。
sub chat { my ($self, @contents) = @_; my @res; my $call = $self->service->Chat(); for my $content (@contents) { my $req = GrpcSandbox::PB::Google::Showcase::V1beta1::EchoRequest->new({ content => $content, }); $call->write($req); my $res = $call->read; push @res, $res->get_content; } $call->writesDone; return \@res; }
Waitメソッドの実装
Waitメソッドは待ち時間を受け取りますが、即座にgoogle.longrunning.Operationを返します。google.longrunning.Operations
serviceが実装されているのでGetOperation
メソッドを定期的に呼び出し、そのoperationが終了したかどうかを確認するようにしてみました。
google.longrunning.Operation
のresponseフィールドはgoogle.protobuf.Any
なので自分でWaitResponse
にデコードする必要があります。
ちなみにgoogle.longrunning.Operation
の詳しい仕様に関してはGoogle AIPsのAIP-151: Long-running operationsにあります。
sub wait { my ($self, $content, $ttl) = @_; my $req = GrpcSandbox::PB::Google::Showcase::V1beta1::WaitRequest->new({ success => { content => $content }, ttl => { seconds => $ttl }, }); my $call = $self->service->Wait(argument => $req); my $res = $call->wait; while (1) { my ($res, $done) = $self->_get_operation($res->get_name); if ($done) { my $wait_res = GrpcSandbox::PB::Google::Showcase::V1beta1::WaitResponse->decode($res->get_value); return $wait_res->get_content; } sleep 1; } } sub _get_operation { my ($self, $name) = @_; my $operations_service = GrpcSandbox::PB::Google::Longrunning::Operations->new( $self->{server}, credentials => $self->{credentials}, ); my $req = GrpcSandbox::PB::Google::Longrunning::GetOperationRequest->new({ name => $name, }); my $call = $operations_service->GetOperation(argument => $req); my $res = $call->wait; return $res->get_response, $res->get_done; }
Blockメソッドの実装
Blockメソッドは受け取った待ち時間分、実際にsleepしてレスポンスを返すというサーバ側の実装になっていますが、ここではあまり関係ないのでエラーを返すときの例として紹介します。
実は->wait
はwantarray
でコンテキストに応じて返り値が変わるようになっており、リストコンテキストで受け取る場合にはレスポンスとstatusを返します。
このstatusというのはGrpc::XSの実装によるとcode, details, metadataというキーを持つhashrefが返され、このキーの順番にgoogle.rpc.Statusのcode, message, detailsに対応します(details→messageなので注意)。
sub block_error { my ($self, $delay, $content) = @_; my $req = GrpcSandbox::PB::Google::Showcase::V1beta1::BlockRequest->new({ response_delay => { seconds => $delay }, error => { code => GrpcSandbox::PB::Google::Rpc::Code::UNKNOWN, message => 'unknown error', }, }); my $call = $self->service->Block(argument => $req); my ($res, $status) = $call->wait; return { code => $status->{code}, details => $status->{details}, }; }
最後にgRPCサーバと通信するテストを書きました。 動かすと裏で立ち上がっているGAPIC Showcaseのコンテナに対して通信します。
% docker-compose exec app bash root@c271acf8b99d:/app# perl t/echo_service.t # Subtest: echo ok 1 ok 2 1..2 ok 1 - echo # Subtest: expand ok 1 1..1 ok 2 - expand # Subtest: collect ok 1 1..1 ok 3 - collect # Subtest: chat ok 1 1..1 ok 4 - chat # Subtest: paged_expand ok 1 ok 2 ok 3 ok 4 1..4 ok 5 - paged_expand # Subtest: wait ok 1 1..1 ok 6 - wait # Subtest: block ok 1 ok 2 1..2 ok 7 - block 1..7
gapic-showcase_1 | 2021/02/06 19:36:44 Received Unary Request for Method: /google.showcase.v1beta1.Echo/Echo gapic-showcase_1 | 2021/02/06 19:36:44 Request: content:"hello" gapic-showcase_1 | 2021/02/06 19:36:44 Returning Response: content:"hello" gapic-showcase_1 | 2021/02/06 19:36:44 gapic-showcase_1 | 2021/02/06 19:36:44 Received Unary Request for Method: /google.showcase.v1beta1.Echo/Echo gapic-showcase_1 | 2021/02/06 19:36:44 Request: content:"world" gapic-showcase_1 | 2021/02/06 19:36:44 Returning Response: content:"world" gapic-showcase_1 | 2021/02/06 19:36:44 gapic-showcase_1 | 2021/02/06 19:36:44 Server Stream for Method: /google.showcase.v1beta1.Echo/Expand gapic-showcase_1 | 2021/02/06 19:36:44 Receiving Message: content:"hello world" gapic-showcase_1 | 2021/02/06 19:36:44 gapic-showcase_1 | 2021/02/06 19:36:44 Server Stream for Method: /google.showcase.v1beta1.Echo/Expand gapic-showcase_1 | 2021/02/06 19:36:44 Sending Message: content:"hello" gapic-showcase_1 | 2021/02/06 19:36:44 gapic-showcase_1 | 2021/02/06 19:36:44 Server Stream for Method: /google.showcase.v1beta1.Echo/Expand gapic-showcase_1 | 2021/02/06 19:36:44 Sending Message: content:"world" <snip>
まとめ
これでPerlでgRPCでの一通りの通信はできました。思った以上にGoogle::ProtocolBuffers::DynamicとGrpc::XSの出来は良く、gRPC Transcodingに頼らずにgRPCで通信するのも選択肢としてはありかもしれません。
ただし動的なスタブコードを使って実装していくのは大変で、protoファイルを見ながら対応しているパッケージをちまちまと書いていく必要がありました。
Goでのprotoc-gen-goを使ったスタブコード生成をしてエディタの補完が効く快適な開発体験と比べると、PerlでもgRPCで通信するのはやっぱり辛いけどなんとかできる状態にはなっています。(プロダクションで使っている事例があれば教えてください)
OpenSSLはどこにいる
この記事は、はてなエンジニア Advent Calendarの15日目です。
OpenSSLに依存しているモジュールをインストールしようと思ったときにライブラリが見つからなくて困ることがあります。
例えばmacOS上でhomebrewを使ってOpenSSLをインストールした場合は /usr/local/opt/openssl
以下に配置されるため、モジュールのインストール時にインクルードパスとライブラリパスを指定しないといけません。
cpanmであれば--configure-args
経由でビルドする際の引数を渡すことができますが、コンパイラにどのように渡されるかはMakefile.PL
を読む必要があります。大変ですね。
そこで作ったのが、いいかんじにOpenSSLのパスを取ってきてくれるモジュールCrypt::OpenSSL::Guessです。
これはNet::SSLeayで使われているOpenSSLのパス解決部分をモジュール化したものです。このモジュールは現在Crypt::OpenSSL::RSAとCrypt::OpenSSL::Randomで使われています。
使い方はMakefile.PL
内に以下のように書くだけです。
use ExtUtils::MakerMaker; use Crypt::OpenSSL::Guess; WriteMakefile( # ... LIBS => ['-lssl -lcrypto ' . openssl_lib_paths()], INC => openssl_inc_paths(), );
実装は素直で、パスを順番に探して見つかったものを返すというものです。
この中に含まれていない場合でも簡単にパスを指定できるように、環境変数OPENSSL_PREFIX
に設定できるようにしています。
sub find_openssl_prefix { my ($dir) = @_; if (defined $ENV{OPENSSL_PREFIX}) { return $ENV{OPENSSL_PREFIX}; } my @guesses = ( '/home/linuxbrew/.linuxbrew/opt/openssl/bin/openssl' => '/home/linuxbrew/.linuxbrew/opt/openssl', # LinuxBrew openssl '/usr/local/opt/openssl/bin/openssl' => '/usr/local/opt/openssl', # OSX homebrew openssl '/usr/local/bin/openssl' => '/usr/local', # OSX homebrew openssl '/opt/local/bin/openssl' => '/opt/local', # Macports openssl '/usr/bin/openssl' => '/usr', '/usr/sbin/openssl' => '/usr', '/opt/ssl/bin/openssl' => '/opt/ssl', '/opt/ssl/sbin/openssl' => '/opt/ssl', '/usr/local/ssl/bin/openssl' => '/usr/local/ssl', '/usr/local/openssl/bin/openssl' => '/usr/local/openssl', '/apps/openssl/std/bin/openssl' => '/apps/openssl/std', '/usr/sfw/bin/openssl' => '/usr/sfw', # Open Solaris 'C:\OpenSSL\bin\openssl.exe' => 'C:\OpenSSL', 'C:\OpenSSL-Win32\bin\openssl.exe' => 'C:\OpenSSL-Win32', $Config{prefix} . '\bin\openssl.exe' => $Config{prefix}, # strawberry perl $Config{prefix} . '\..\c\bin\openssl.exe' => $Config{prefix} . '\..\c', # strawberry perl '/sslexe/openssl.exe' => '/sslroot', # VMS, openssl.org '/ssl$exe/openssl.exe' => '/ssl$root', # VMS, HP install ); while (my $k = shift @guesses and my $v = shift @guesses) { if ( -x $k ) { return $v; } } (undef, $dir) = check_no_path() and return $dir; return; }
PerlにはThere's more than one way to do it(やり方はいくつもある)というモットーがありますが、それに続くbut sometimes consistency is not a bad thing either(ときには共通のやり方があっても悪くはない)という言葉があるように、各モジュールが行っていたOpenSSLのパスの解決の仕組みを共通のやり方で出来るようにモジュール化したという話でした。
UCSB iCTF 2018 - fantasticiot, hero_text_adventure
attack and deference形式のCTFにオンラインで参加した。dodododoは19位。
sshできるサーバが1台与えられて、そこで8つのサービスを正しく動かしつつ、攻撃と防御を行う。
flagは運営からサービスが正しく動いているかどうかの確認と一緒に送られてくる。例えば秘密のメモアプリみたいなのがあったとして、正規の方法だとメモの閲覧にはパスワードが必要だけど、サービスに残された脆弱性を使うと任意のメモを見られるになればflagを入手して提出することで攻撃できる。
サービスはxinetd経由で起動するようになっていて、サービスの脆弱性を潰すためバイナリを書き換えたり、途中にバリデーション用のスクリプトを挟んだりしてflagを持ち出されないように防御できる。
このCTF専用のサービス一覧/参加チーム取得やフラグ提出用のクライアントがあって、それを使うと手軽に攻撃の自動化ができる。 事前にサンプル が渡されたのだけど、結構ミスっていて困っていた。事前にテストして欲しい……
fantasticiot
% file ./fantasticiot ./fantasticiot: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=baa3e0fa48be4715a10784c2dd5e7d3adb5d9181, not stripped
冷蔵庫サービス。static linkされていて一瞬ぎょっとするけど、中身は単純にJSON経由で冷蔵庫に入れたりすることができるサービス。flagはsetflagで保存されたflag。
- setflag
{"token": "A", "flag": "B", "id": "2", "service": "flag", "op": "setflag"}
flag/1
に書き込む
- getflag
{"token": "A", "id": "1", "service": "flag", "op": "getflag"}
flag/1
から取得する- ただし正しいtokenである必要がある
- addfridge
{"content": "AAAAAAAAAA", "item": "1", "service": "fridge", "op": "addfridge"}
fridge/1
に書き込む
- getfridge
{"item": "1", "service": "fridge", "op": "getfridge"}
fridge/1
から取得する
脆弱性
こういう雑なバリデータを挟むことで回避できる。
import sys import json src = sys.stdin.readline() try: data = json.loads(src) if data['op'] == 'getfridge': assert data['service'] == 'fridge' assert '/' not in data['item'] sys.stdout.write(src) except: pass
- strncmpにbackdoorが仕掛けられている
static linkされているのはこういうことだったのか!上位チームでも意外と気づかなかったチームが結構いた。
つまり、getflagの際にtokenを victor
に指定すればtokenチェックが問答無用に突破可能。
def exp(s, flag_id): payload = { "service": "flag", "op": "getflag", "id": flag_id, "token": "victor", } s.send(json.dumps(payload) + '\n') return json.loads(s.recvuntil('\n'))['flag']
hero_text_adventure
% file ./hero_text_adventure ./hero_text_adventure: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=98aac0bf05483d6328b1640625404c533c785999, not stripped % ./hero_text_adventure Hello superhero, are you continuing your previous adventure? (y/N) n Welcome to the beta test of the Marvel Super Hero text adventure! Enter your name: test Pick a character 1) Star Lord 2) Dr Strange 3) wanda maximoff 4) Hulk 5) Thor 1 test, what would you like to do? 1) BATTLE! 2) buy weapon 3) equip weapon 4) give weapon a new name 5) load game 6) exit 6 Exiting. Save game? (y/N) y enter a password to protect your save file secretpassword! id of your adventure: 7906397325484763355
アドベンチャーゲーム。戦ったり、武器を買ったり、データをロード/セーブできる。flagはセーブされているデータのname部分。
脆弱性
- weaponのhandlerのアドレス書き換え
1) BATTLE!
のときに任意のアドレスに対して飛べるようになる- readlineの最初(0x400C8C)に飛ぶことで、bssのプレイヤー名のあるアドレス(0x603240)以降の書き換えができる
- そこから装備中の武器のアドレスを書き換えると、
4) give weapon a new name
から任意のアドレスへの書き込みができるようになる - GOTにあるstrcmpをstrchrに書き換えるとデータのロード時のパスワードチェックで、strcmpが
strchr("real_password", "input")
になり、これはNULLを返すので任意のデータをロードできるようになりflagが入手できる
exploit: hero_text_adventure · GitHub
防御に関しては、give weapon a new nameのときのreadlineが36バイト読み込むのを32バイトだけ読み込むようにバイナリを変更した。
感想
自分の現在の順位よりも上のチームにしか攻撃できない(flagの提出もできず、サーバへのアクセスも遮断されている)ようになっていて珍しいルールだった。よくあるA&D形式のCTFだと防御がボロボロだと上位チームに攻撃されて、点数が減る一方で最後までやる気がなくなってしまうのだけど、これならまだ最後までできる。
一見よいルールに見えるのだけど、順位をわざと下げて攻撃対象を増やして、一度に大量の得点を取得することが可能で(もちろん攻撃が塞がれてなければだけど)割と入れ替わりが激しく、崩壊していた気がする。
今回はRuCTFEのようにVPNの提供はなく、サーバが与えられて手元から他チームに攻撃するときはsocks経由でやってくれ、というかんじだったので運営のことを考えると準備の手間をあまり考える必要なく、これなら手軽にA&D形式のCTFを開催できるのではという気がした。
ただチェックシステムを準備するのが大変という話はありそうだけど、ちょっと調べたらRuCTFEで使われているコードが公開されていた。A&D形式のCTF増えて欲しい。
YAPC::Okinawaで「Perlコーディングテクニック2018」という話をしました
最近の便利Perl情報や好きなモジュールの話をしました。 トーク応募したときには話したかった細かい話題がいくつかあったのですが、20分では収まらなかったのもありクラスビルダやクラスローダ、バリデータ、Type::Tinyの話になりました。
トークの中で紹介した、拙作のSmart::Args::TypeTinyが結構好きで最近よく使っています。
Smart::Args::TypeTiny - We are smart, smart for you - metacpan.org
詳しい使い方やSmart::Argsと比較したときのメリットをあまり説明できなかったのですが、Smart::Args::TypeTinyについては昨年 id:papix さんが紹介している記事があります。(ありがとうございます!!!)
自分のトーク後の id:shoichikaji さんのトークの冒頭では、手元環境ではstricturesの
PERL_STRICTURES_EXTRA
を設定しているという話がありました。便利ですね。
strictures - turn on strict and make most warnings fatal - metacpan.org
参加者の皆様並び運営スタッフの皆様お疲れ様でした。次回も楽しみにしています。
YAPC::Fukuokaで「新時代のテストフレームワークTest2」という話をしました
トークの内容はTest2
の導入から便利情報を紹介したりしました。少しでもTest2
使う人が増えるといいですね。
スライド中にも触れていますが、基本的にはTest2
に移行するにあたってはTest2::Plugin::UTF8
を使うようにすれば大丈夫かなと思います。
一部Test::Class
のハックをTest2
に対応するといった特殊な例も紹介しました。スライドでは時間の都合上省いたのですが、Test::Class
は意外と扱いが難しく、テストファイルから読み込まれるモジュールでTest::More
を直接使っていると$TODO
が動かなくなるというケースもありました。Test::More
のok
を直接呼んでいる場合はTest2::API
のcontext
で書き換える必要があります。
また、Test::Warnings
でテスト中で警告が出ていたらfailする機能(Test::Builder::done_testing
を書き換えている lib/Test/Warnings.pm - metacpan.org)が動かなくなっていることに関してはTest2::Plugin::NoWarnings
を使うことにより代用することができます。
Test2::Plugin::NoWarnings - Fail if tests warn - metacpan.org
参加者の皆様並び運営スタッフの皆様お疲れ様でした。次のYAPCは沖縄で開催するとのことで楽しみにしています。