Skip to content

Instantly share code, notes, and snippets.

@Tosainu
Created June 25, 2016 13:29
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save Tosainu/293f167cde1a67ea7e0c18eed975d11c to your computer and use it in GitHub Desktop.
seccamp'16の応募用紙です.

共通問題.1

(1) あなたが今まで作ってきたものにはどのようなものがありますか? いくつでもいいので、ありったけ自慢してください。

twitpp

C++からTwitter APIを叩くためのライブラリです. C++を勉強し始めた頃, 同年代の人たちが独自のTwitterクライアントや連携Webサービスを作っていたことに影響され, 自分もTwitterクライアントを作ろう, まずはC++でTwitter APIを叩けるようにしなくてはということでこのライブラリを作りました.

REST APIはもちろん, PINを使ったOAuth認証やStreaming APIにも対応しています. 特にStreaming APIはデータが到着したときに登録した関数が呼ばれるイベント駆動になっており, レスポンスの待ち時間でCPUを食い潰すことがありません. 他言語のTwitterライブラリのようにイベントハンドラとしてラムダ式を渡すことも可能です.
また, C++の外部ライブラリにありがちな "Qt等の大きなフレームワークに依存している", "独自の文字列型が使われている", "命名規則が標準ライブラリ(snake_case)と違う" といったことによく苦しんでいた経験から, twitppではこのようなことを極力なくすように実装しています. BoostとOpenSSLだけに依存しているだけなので小規模なソフトウェアでの利用にも向いているほか, 文字列型にstd::stringを使い, snake_caseのインターフェースを提供しているので, 標準ライブラリと相性が良いのも特徴です.

Raspberry Piラジコン

使われなくなったラジコンにRaspberry Pi, Webカメラ, WiMAXルータを搭載し, ブラウザ経由で操縦できるようにしたものです. 当時友人たちと結成していた学校非公式のサークル "ジャンク再生研究会(仮)" 最初の活動として文化祭に展示するために作成しました.

WebブラウザからRaspberry Piで起動したWebサーバにアクセスすると, 搭載したWebカメラの映像をリアルタイムで見ながらページ上のボタンで操縦することができます. また, WiMAXルータを搭載しているため, ラジコンがある場所にWiMAXの電波さえ届けばバッテリが切れるまで世界のどこからでも操縦するといったことも可能です.

(2) それをどのように作りましたか?ソフトウェアの場合にはどんな言語で作ったのか、どんなライブラリを使ったのかなども教えてください。

twitpp

開発に至った経緯の詳細や, 開発の流れは共通問題2に記述しました.
開発言語はC++11で, 依存している外部ライブラリとしてネットワークI/OにBoost.Asio, URLやHTTPヘッダ, 一部のTwitterからのレスポンスのパースにBoost.Xpressive, HMAC-SHA1のエンコードやHTTPS通信のためにOpenSSLを使っています. また, ビルドツールとしてCMakeを使っています.

Raspberry Piラジコン

全体の制御にRaspberry Pi 1 Model B, モータドライバにTA7291Pを使った自作の回路, 電源にUSB2ポートのモバイルバッテリーを使っています. それ以外のモータ等は元々ラジコンについていたものを利用しました.
ソフトウェアの面では, サーバ, ブラウザともにJavaScript(サーバ側はNode.js)で開発を行い, Raspberry PiにはArch Linux ARM, Webカメラのストリーミングにmjpg_streamer, GPIOの制御にWiringPi, Webサーバとブラウザ間の通信にSocket.IOを使いました.

Raspberry Piでラジコンを制御するということで, まずWebブラウザとRaspberry Pi間でリアルタイムで通信する良い方法が無いかと探しました. 当時, 丁度Node.jsが有名になってきた頃で, 特にSocket.IOを使ったサンプルをよく目にしたことから, これを使うことにしました. まず簡単なアプリケーションを作りSocket.IOに触れ, 次にラジコンの制御に必要な前進/後退, ハンドルを切る方向, スピードといったパラメータをやり取りできるようにしました.
これに並行して, Webカメラの映像を配信するソフトウェアを試したり, モータドライバ等の回路を組み立てたりし, 最後にそれらをまとめて完成しました.

(3) 開発記のブログなどあれば、それも教えてください。コンテストなどに出品したことがあれば、それも教えてください。

twitpp

Raspberry Piラジコン

共通問題.2

(1) あなたが経験した中で印象に残っている技術的な壁はなんでしょうか?(例えば、C言語プログラムを複数ファイルに分割する方法)

"技術的な壁" とは少し異なってくるかもしれませんが, プログラミング言語入門書レベルからの脱出に苦労しました.
具体的には, C++を勉強しているときに, 入門書や初心者向け解説Webサイトにあるような基本的な言語機能(四則演算, 制御構文やクラスといった文法)を理解することができても, "じゃあこれで何が作れるんだ, 世の中で動いている沢山のアプリケーションはどうやったら作れるんだ" といったモヤモヤからなかなか抜け出すことができず苦労しました.

(2) また、その壁を乗り越えるために取った解決法を具体的に教えてください。(例えば、知人に勧められた「○○」という書籍を読んだ)

私がこうしたモヤモヤに陥っていたとき, 同年代のプログラムを書いている人たちがTwitterの独自クライアントや連携Webサービスを作っていたことに影響され, じゃあ自分もTwitterクライアントを作るぞというすぐには達成できない目標を立てました. そして, それに必要となる技術を調べながら少しづつ実装していき, とにかく入門書に載っていないようなプログラムを自分で書こうとすることで乗り切りました. この最初の成果物が共通問題2で挙げたtwitppになります.

twitppは以下のような流れで開発しました.


1. まずTweetするだけのプログラムを作成しよう

TwitterとデータをやりとりするためにはTwitterのサーバーと通信しなければいけない
→ HTTP, HTTPSって何だろう
→ C++でHTTPリクエストをしてみよう

Twitter APIはOAuthというものを使っているらしい
→ OAuthがどんなものか調べよう
→ OAuthで必要なパラメータをC++で生成してみよう

必要なものは揃った, Twitterにリクエストしてみよう
https://twitter.com/myon___/status/405343973524250625

2. どこからでもこのプログラムが呼べるようにライブラリ化しよう

→ twitpp誕生


今もまだこのモヤモヤから完全に抜け出せたという感覚はありませんが, 本格的な勉強を始めて数年の間に入門書には書いていない比較的大きなソフトウェアを書いたり, 参加している課外活動で新しいメンバーにプログラミングを教える役を任されるくらいには成長することができました.
また, このTwitterライブラリ開発を通してTCP, HTTP, HTTPSなどのネットワーク技術に触れたり, HTTPやOAuthについて調べた際にはRFCの文章を読んだりと, C++プログラミング以外にも関連する多くの知識を得ることができ, こうした経験もモヤモヤから抜け出す一因になったのではないかと思っています.

(3)その壁を今経験しているであろう初心者にアドバイスをするとしたら、あなたはどんなアドバイスをしますか?

実際に学校などで "プログラミング勉強したいんだけど何すればいい?" といった質問を何度かされているのですが, そんなときはとりあえず自分で一つのソフトウェアを作ることを勧めています.
とは言っても, どんなものを作れば良いのかわからないと返されるのが大半なので, まず実際に同じ道を目指しているプログラマが何を作っているのかや, その言語の外部のライブラリを調べながらどんなことができそうかを見つけ, とにかく黒い画面の外にあるものを動かしてみようと言っています. (GUIアプリケーション, Webアプリケーション, 画像処理やロボット制御など)
自分自身がそうだったように, 実際に何かを動かしていくことでプログラミングの楽しさを感じ, またその過程で関連する知識が増えていくことで, 自分からより高度なことに挑戦していけるようになるのではないかなと考えています.

共通問題.3

(1) あなたが今年のセキュリティ・キャンプで受講したいと思っている講義は何ですか?(複数可)
そこで、どのようなことを学びたいですか?なぜそれを学びたいのですか?

7-A ID連携基礎

講義概要にあるように, SNSをはじめ多くの場所でOAuthやOpenIDというワードを見かけるようになったことから, その詳しい仕組みやセキュリティ面について知っておくことは重要だと思ったからです.
共通問題1で挙げた自作のTwitterライブラリを作成した際にはOAuth認証を行う部分はライブラリを使わずに自分で実装したという経験があるので, この講義を通してOAuthにだけでなく, OpenID Connectといった実装したことのない他のプロトコルについてもしっかり学びたいと思っています.

1-B BareMetalで遊ぶRaspberryPi 入門編

Raspberry PiをAVR等のマイコンと同じようなものとして見たとき, GPIOの操作やUART等を利用するためにLinuxをブートさせるというのは少し大げさだなと以前から感じていたからです. また, Twitterで書籍 "BareMetalで遊ぶ Raspberry Pi" が紹介されているを見て興味を持っていたからです.
低レイヤな世界のプログラミングやそれに必要な技術を学びつつ, 手持ちのホコリをかぶってしまったRaspberry Piの新しい使い方も見つけられたらいいなと思っています.

5-B USBメモリからブートしてみよう

普段使っているArch Linuxをインストールするために起動可能なUSBメモリを作成したり, grub-install等のコマンドを打つなど, ブートローダを手動でインストールする機会はあっても, その際にMBRに書き込まれるデータについては今まで全く意識したことがなかったからです. また, 自分で書いたアセンブリの言語プログラムを, USBメモリという身近なものを使いながら直接ブートするという点で非常に興味深かったからです.
PCのブートプロセスを学びながら, 自分で書いたアセンブリ言語のプログラムが "ブートする" という感動を味わってみたいです.

集中講義 OS/VMレイヤで実現する圧倒的に高速なパケットフィルタ/サンドボックス

2015年8月に参加したカーネル/VM探検隊@名古屋 1回目や, その他の勉強会のライブ配信で浅田拓也さんによるScyllaDB社のSeastarやネットワークI/Oの高速化についての発表を何度か拝見しており, "カーネルをバイパス" といった言葉から難しそうだなと思いながらも, 高速にパケットを捌くために開発されたというIntel DPDKに惹かれるものがあったからです.
ネットワークについての知識は浅いため, 実際にこの講義についていけるか不安な面はありますが, 今まで扱ったことのない10Gbpsの高速ネットワークに触れながら, パケット単位での処理を通してネットワークについての知識を深めたいと思っています.
(現在公開されている時間割では5-Bと重なっており, どちらを受講しようか非常に迷っています)

(2) あなたがセキュリティ・キャンプでやりたいことは何ですか?身につけたいものは何ですか?(複数可)  自由に答えてください。

1つ目は, 低レイヤの世界に入門したい, 低レイヤ開発に必要な知識を身に着けたいということです.
数年前にセキュリティ・キャンプを知って以来, "セキュアなシステムを作ろうクラス" や "低レイヤトラック" でOSやVMの開発, Linuxカーネルレイヤでのセキュリティ機構の開発, ルーターの自作などを行う講義が行われていることにとてもに興味を持っていました. というのも, 初めてのプログラミング体験がマイコンだったこともあり, いずれは今使っているPCやスマートフォンなどを始めとする身近なデバイスもOSやそれを搭載するハードウェアのレイヤから扱い, 特にパフォーマンスの最適化やセキュリティ面での強化を行うことに興味があったからです.
こうした低レイヤを扱えるようになる第一歩として, 今年のセキュリティ・キャンプではIoTトラックの講義を中心に, 低レイヤネットワークに関連する講義も受講したいと考えています.

2つ目は, 高い技術を持った方々の中での講義を通して自分の技術力を上げていきたいということです.
私のプログラミング学習がそうだったように, 何か新しいことを学ぶときに周りに高い技術を持った人がいるという状況は大きな力になると考えています. 特に, そうした同年代の人と意見を交わすことは自分の足りない知識を明らかにできるだけでなく, 私も負けてられないぞという思いから大きなモチベーションの向上にもなった経験があります.
しかしながら, 私は電気科に通っているので普段セキュリティや低レイヤに強い人に出会うことは滅多にありませんし, 専門の教員による講義を受けるという機会もありません.
セキュリティ・キャンプの講義やグループワークを通し, こうした高い技術を持った方々の影響を受けながら, 少しでも多くの意見交換ができたら良いなと思っています. また, 実際のキャンプ参加者がそうであったように, キャンプ終了後も何らかの課題に取り組むなどの参加者同士の交流を続けていくことで, 更に自分の技術力向上に繋げていきたいと思っています.

選択問題.3

RAMは主記憶装置、HDDやSSDなどは補助記憶装置と呼ばれます。一般にCPUは主記憶装置上のプログラムしか実行できません。ではなぜ、私たちは普段から補助記憶装置に書き込んだプログラムを実行できているのでしょうか?パソコンの電源を入れてからのストーリーを考えてみてください。

何度かワンチップマイコンを扱ったことがあるので, 例えばAVRやPICでは電源を入れるとreset vectorである0番地からプログラムが開始されると学びました. 一般的なPCに搭載されているCPUも同じようにreset vectorに相当するものがあり, 電源を入れると接続された主記憶装置のその番地からプログラムを読み込み実行していると考えました. ここでPCのreset vectorに相当する番地にあるプログラムの正体が何なのかということになりますが, いろいろと考えた末, 最初に実行されるのはBIOSやUEFIといったマザーボードのファームウェアで, CPUがreset vectorでファームウェアのROMにアクセスできるようになっているのではないかという結論に至りました.
ファームウェアは接続されたデバイスの探索や初期化などを行った後, MBRやESP内のアプリケーションといったOSを起動するためのプログラムを補助記憶装置から読み出し/実行し, やっとOSが起動します. ここまで来れば, ユーザは補助記憶装置に書き込んだ任意のプログラムを実行可能になります.

選択問題.4

突然だが、RH Protocolで用いられるRHパケットのフォーマットを以下に示す。なおRH Protocolは実在しないプロトコルであり、その内容について特に意味は無い。

Format of RH Packet  
|————————|—————...—|———————…—|———————...—|———————...—|  
|  Magic (2byte)     | Source(20byte)|Destination(20byte)| Data Length(4byte)| Data( variable )      |  
|————————|—————...—|———————…—|———————...—|———————...—|
char Magic [2];
char Source[20]; /* null(‘\0’) terminated ascii strings */
char Destination[20]; /* null(‘\0’) terminated ascii strings*/
uint32_t DataLength; /* min 0, max 4,294,967,295 */
char Data[DataLength]; /* null(‘\0’) terminated ascii strings */

バイトオーダーはbig endian(network byte order)とする。

添付するバイナリは、とあるRHストリームのうち片方向のみを抽出したものである。このバイナリストリームを読み込み、1つのRHパケットが以下の条件のすべてにマッチするときに標準出力に文字列”PASS”、 それ以外の場合は”REJECTED”と表示するCもしくはC++のプログラムを記述し、実行結果と共に提出せよ。また、マッチングにかかるCPUサイクル及びメモリ使用量を計測し記載した場合、評価に加味する。

Condition(条件)1: Magicがchar[0] = ‘R’char[1] = ‘H’であること。
Condition 2: Sourceが”rise-san”または”cocoa-san”であること。なお、”RiSe”や”Cocoa”など、小文字大文字が混ざっていても、マッチさせること。
Condition 3: Destinationが”Chino-chan”または”Chino"であること。なお、cond. 2と同じく、小文字大文字が混ざっていても、マッチさせること。
Condition 4: Sourceが”cocoa-san”かつDestinationが”Chino”の場合はREJECTする。
Condition 5: Dataに下記の文字列を厳密に含むこと。

char** valid_order_brand =
{
    “BlueMountain"
    “Columbia”,
    “OriginalBlend"
};

Condition 6: Dataに下記の文字列を厳密に含まないこと。なお、cond. 4よりも、cond. 5が優先される。

char** invalid_order_brand =
{
    “DandySoda"
    “FrozenEvergreen”
};

(実行結果と作成したプログラムは文章の最後に示します)

マッチングにかかるCPUサイクルを, 以下の関数をcheck()関数の前後で実行し, その差を関数の最後で表示するように改造して, 12000回分(PASSするデータ24件 x 500回)の平均を計測しました.
(環境: Arch Linux x86_64, clang 3.8.0, clang++ -std=c++14 -O2 rhp.cc -o rhp)

inline uint64_t rdtsc() {
  uint64_t low, high;
  __asm__ volatile("rdtsc" : "=a"(low), "=d"(high));
  return (high << 32) | low;
}

しかし, 実行毎やPCによって値は大きく変動し, あるPCでは80009000サイクル, またあるPCでは50006000サイクルという結果となり, 正確な値は計測できませんでした.

$ for i in {1..100}; do ./rhp pyonpyon.rh; done | grep -o '[0-9]\+' | awk '{ sum += $1 } END { print sum / NR }'
8069.4

メモリ使用量の計測は, 具体的な手段が思いつかなかったため行いませんでした.

実行結果

$ ./rhp pyonpyon.rh
PASS
PASS
PASS
PASS
PASS
PASS
PASS
PASS
PASS
REJECTED
PASS
PASS
PASS
REJECTED
REJECTED
PASS
PASS
PASS
REJECTED
REJECTED
REJECTED
PASS
PASS
PASS
REJECTED
REJECTED
REJECTED
REJECTED
PASS
PASS
PASS
REJECTED
REJECTED
REJECTED
REJECTED
REJECTED
PASS
PASS
PASS
REJECTED
REJECTED
REJECTED
REJECTED
REJECTED
REJECTED

プログラム

別のGistに分けて上げました.
https://gist.github.com/Tosainu/9e435b79ab134d26025287aa4d065bc4

選択問題.8

以下のダンプはあるプログラムのobjdumpの結果である。このプログラムが行っていることを調べ、その結果を記述してください。完全には分からなくても構いませんので、理解できたところまでの情報や調査の過程で使ったツール、感じたこと等について記述してください。

$ objdump -d challenge00
challenge00:     ファイル形式 elf64-x86-64

セクション .text の逆アセンブル:

0000000000400080 <.text>:
 400080:    68 19 01 40 00           pushq  $0x400119
 400085:    6a 01                    pushq  $0x1
 400087:    68 06 01 40 00           pushq  $0x400106
 40008c:    68 19 01 40 00           pushq  $0x400119
 400091:    68 29 01 40 00           pushq  $0x400129
 400096:    6a 3c                    pushq  $0x3c
 400098:    68 02 01 40 00           pushq  $0x400102
 40009d:    68 10 01 40 00           pushq  $0x400110
 4000a2:    48 b8 36 15 1b 25 67     movabs $0x63391a67251b1536,%rax
 4000a9:    1a 39 63 
 4000ac:    50                       push   %rax
 4000ad:    68 02 01 40 00           pushq  $0x400102
 4000b2:    6a 00                    pushq  $0x0
 4000b4:    68 06 01 40 00           pushq  $0x400106
 4000b9:    68 14 01 40 00           pushq  $0x400114
 4000be:    68 0c 01 40 00           pushq  $0x40010c
 4000c3:    68 02 01 40 00           pushq  $0x400102
 4000c8:    68 26 01 40 00           pushq  $0x400126
 4000cd:    68 14 01 40 00           pushq  $0x400114
 4000d2:    6a 07                    pushq  $0x7
 4000d4:    68 0a 01 40 00           pushq  $0x40010a
 4000d9:    6a e0                    pushq  $0xffffffffffffffe0
 4000db:    68 08 01 40 00           pushq  $0x400108
 4000e0:    68 19 01 40 00           pushq  $0x400119
 4000e5:    6a 08                    pushq  $0x8
 4000e7:    68 04 01 40 00           pushq  $0x400104
 4000ec:    6a 00                    pushq  $0x0
 4000ee:    68 1c 01 40 00           pushq  $0x40011c
 4000f3:    6a 00                    pushq  $0x0
 4000f5:    68 06 01 40 00           pushq  $0x400106
 4000fa:    6a 00                    pushq  $0x0
 4000fc:    68 02 01 40 00           pushq  $0x400102
 400101:    c3                       retq   
 400102:    58                       pop    %rax
 400103:    c3                       retq   
 400104:    5a                       pop    %rdx
 400105:    c3                       retq   
 400106:    5f                       pop    %rdi
 400107:    c3                       retq   
 400108:    5d                       pop    %rbp
 400109:    c3                       retq   
 40010a:    59                       pop    %rcx
 40010b:    c3                       retq   
 40010c:    48 01 ec                 add    %rbp,%rsp
 40010f:    c3                       retq   
 400110:    48 39 06                 cmp    %rax,(%rsi)
 400113:    c3                       retq   
 400114:    80 34 0e 55              xorb   $0x55,(%rsi,%rcx,1)
 400118:    c3                       retq   
 400119:    0f 05                    syscall 
 40011b:    c3                       retq   
 40011c:    48 89 e6                 mov    %rsp,%rsi
 40011f:    41 5a                    pop    %r10
 400121:    c3                       retq   
 400122:    48 89 f1                 mov    %rsi,%rcx
 400125:    c3                       retq   
 400126:    48 ff c9                 dec    %rcx
 400129:    75 01                    jne    0x40012c
 40012b:    c3                       retq   
 40012c:    41 5a                    pop    %r10
 40012e:    c3                       retq   

x86(-64)のアセンブリはほとんど触れたことがないので, Intel® 64 and IA-32 Architectures Software Developer Manualsやいくつかの解説サイトを参照しながら, わからないところは選択問題10のコードにも出てきたインラインアセンブリを使った簡単な再現コードを書いたり, そのプログラムをgdbで値を見ながらステップ実行することで調査を行いました.

参考にしたWebサイト

まず, 0x400080~0x4000fcでは, stackにたくさんの値をpushしているのがわかります. この内のいくつかはobjdump中のアドレスと一致しており, 何か関係がありそうだと予想できました.
その後, retqやpop等の命令が出てきます. ret(q)は呼び出された関数から戻るときに使われる命令のようですが, stackからpopしてそのアドレスに移動する命令とも考えられるようなので, これをふまえて以下のようにプログラムを追っていきました.

0x400101: retq                        ; goto 0x400102

0x400102: pop    %rax                 ; $0x0 -> %rax
0x400103: retq                        ; goto 0x400106

0x400106: pop    %rdi                 ; $0x0 -> %rdi
0x400107: retq                        ; goto 0x40011c

0x40011c: mov    %rsp,%rsi            ; %rsp -> %rsi
                                      ; %rsp: 現在のstack pointer (値: 0x0)
0x40011f: pop    %r10                 ; $0x0 -> %r10
0x400121: retq                        ; goto 0x400104

0x400104: pop    %rdx                 ; $0x8 -> %rdx
0x400105: retq                        ; goto 0x400119

0x400109: retq                        ; goto 0x400108

0x400108: pop    %rbp                 ; $0xffffffffffffffe0 -> %rbp
0x400109: retq                        ; goto 0x40010a
0x40010a: pop    %rcx                 ; $0x7 -> %rcx
0x40010b: retq                        ; goto 0x400114

https://en.wikibooks.org/wiki/X86_Assembly/GAS_Syntax#Address_operand_syntax
(base register, offset register, scalar multiplier) を表す文法らしいので,
(%rsi,%rcx,1) => (%rsi + %rcx * 1) => %rsiが指す値の%rcxバイト目(LEなので)のアドレス

0x400114: xorb   $0x55,(%rsi,%rcx,1)  ; (%rsiの%rcxバイト目) ^= 0x55
0x400118: retq                        ; goto 0x400126

0x400126: dec    %rcx                 ; %rcx(=7) -= 1
0x400129: jne    0x40012c             ; if %rcx(=6) != 0
                                      ;   goto 0x40012c
                                      ; else
0x40012b:        retq                 ;   goto 0x400102

0x40012c: pop    %r10                 ; $0x400102 -> %r10
0x40012e: retq                        ; goto 0x40010c

0x40010c: add    %rbp,%rsp            ; %rsp += 0xffffffffffffffe0(-32)
                                      ; => stack pointerを4戻す
0x40010f: retq                        ; goto 0x400114

0x400102: pop    %rax                 ; $0x40010c -> %rax
0x400103: retq                        ; goto 0x400114

0x400114: xorb   $0x55,(%rsi,%rcx,1)  ; (%rsi + %rcx * 1) => %rsiの0バイト目とxor
0x400118: retq                        ; goto 0x400106

この範囲は以下のCのコードと同等の処理を行っていると考えられます. よって, 最終的に(0x400106にジャンプする時)%rsiが指す値は0x5555555555555555になると思います.

int rcx       = 0x07;
int64_t value = 0x00;
int64_t* rsi  = &value;

do {
  *rsi ^= (int64_t)0x55 << 8 * rcx;
  rcx--;
} while (rcx != 0);

*rsi ^= (int64_t)0x55 << 8 * rcx;
0x400106: pop    %rdi                 ; $0x0 -> %rdi
0x400107: retq                        ; goto 0x400102

0x400102: pop    %rax                 ; $0x63391a67251b1536 -> %rax
0x400103: retq                        ; goto 0x400110

0x400110: cmp    %rax,(%rsi)          ; compare %rax, %rsi
0x400113: retq                        ; goto 0x400102

0x400102: pop    %rax                 ; $0x3c -> %rax
0x400103: retq                        ; goto 0x400129

0x400129: jne    0x40012c             ; if %rax != %rsi
                                      ;   goto 0x40012c
                                      ; else
0x40012b: retq                        ;   goto 0x400119

0x40012c: pop    %r10                 ; $0x400119 -> %r10
0x40012e: retq                        ; goto 0x400106

0x400106: pop    %rdi                 ; $0x1 -> %rdi
0x400107: retq                        ; goto 0x400119

0x400119: syscall                     ; %rax = 0x3c
                                      ; => exit(%rdi);

ここで, 先ほど生成した 0x5555555555555555 と 0x4000a2で movabs $0x63391a67251b1536,%rax; push %rax した値を比較し, 一致しなければexit(1), 一致すればexit(0)で終了していることがわかりました. 実際にどのように動作するかを確認するために以下のコードを書いて実行したところ, 結果は1となりました. おそらくこのプログラムもreturn code 1で終了するのではないかと思います.

#include <stdio.h>

__asm__(
    "sq08:\n\t"
    "mov    $0x5555555555555555, %rax\n\t"
    "push   %rax\n\t"
    "movabs $0x63391a67251b1536, %rax\n\t"
    "push   %rax\n\t"

    "pop    %rax\n\t"
    "cmp    %rax, (%rsp)\n\t"
    "jne    nequal\n\t"
    "mov    $0x0, %rax\n\t"
    "jmp    fin\n\t"
    "nequal:"
    "mov    $0x1, %rax\n\t"

    "fin:\n\t"
    "pop    %rcx\n\t"
    "ret");
int sq08();

int main() {
  printf("%d\n", sq08());
}

選択問題.10

まずは以下のプログラムを物理PCと複数の仮想化ソフトウェア(qemu、 VMware、 Virtual PCなど)を使って実行し、それぞれの結果の違いを確認してください。そして、なぜそうした結果が得られたのか、物理PCと同じ振る舞いを実現するには仮想化ソフトウェアをどのように改造すればよいかを考察し、その内容を記述してください。

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

void sighandler()
{
   printf("OK\n");
   exit(0);
}

__asm__("check00:\n\
   mov $0x564D5868, %eax\n\
   mov $0xa, %cx\n\
   mov $0x5658, %dx\n\
   in %dx, %eax\n\
   ret\n\
");
void check00();

__asm__("check01:\n\
   .byte 0xf3,0xf3,0xf3,0xf3,0xf3\n\
   .byte 0xf3,0xf3,0xf3,0xf3,0xf3\n\
   .byte 0xf3,0xf3,0xf3,0xf3,0xf3\n\
   ret\n\
");
int check01();

__asm__("check02:\n\
   .byte 0x0f,0x3f,0x07,0x0b\n\
   ret\n\
");
int check02();

int main(int argc, char **argv)
{
   int cmd;
   if(argc == 2){
       cmd = atoi(argv[1]);
   }else{
       printf("USAGE: %s <command>\n", argv[0]);
       exit(1);
   }
   signal(SIGSEGV, sighandler);
   signal(SIGILL, sighandler);
   switch(cmd){
   case 0: check00(); break;
   case 1: check01(); break;
   case 2: check02(); break;
   default: exit(1);
   }
   printf("NG\n");
   return 1;
}

以下の環境で実験を行いました. Virtual PCを除き, ホスト, ゲストともにOSにはArch Linux x86_64を使いました.

その結果, 以下のようになりました.

check00 check01 check02
ホスト OK OK OK
VMware NG OK OK
QEMU OK NG OK
KVM OK OK OK
Virual PC OK OK NG

調べていると以下のページにたどり着き, check00~check02はプログラムが仮想マシン上で動かされているかを検出する方法として知られているコードであることがわかりました.

https://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458
VMwareにはソフトウェアがVMwareハイパーバイザ上で実行されているかを検出するためのIOポートが実装されており, eaxを0x564D5868, ebxを0xFFFFFFFF, ecxを10にしたうえで, ポート0x5658に対してIN操作をすることで検出できる.
(このポートはx86 ISA領域外にあるようなので, VMware以外ではsegfaultとなったと考えられる)

https://www.damballa.com/downloads/a_pubs/CCS08.pdf (5.1.5 Emulator Resistant Malware)
15のREP prefixes(0xf3)を1つの命令に付けて実行すると, x86の最大の命令長は15byteであるのでillegal instruction exceptionとなるはずだが, QEMUはこの例外を発生しないらしい.

http://www.codeproject.com/Articles/9823/Detect-if-your-program-is-running-inside-a-Virtual
Virtual PCでは仮想マシンと仮想化ソフトウェア間の通信のために無効な命令(0x0f, 0x3f, 0x07, 0x0b)を使っているらしく, この命令はVPC上で実行してもエラーにならない.

よって, 物理PCと同じ振る舞いをさせるには

VMware: VMware hypervisor portを無効にする
QEMU: 15byteより長い命令は無効と処理するようにする
Virtual PC: 無効な命令を使用するのをやめる 別の方法でVMとソフト間の通信を行う

ように改造する必要があると考えられます.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment