ブログ未満のなにか

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

セキュリティキャンプ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は重要な位置を占めていくと考えている。

さいごに

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