protocol buffersバイナリデータを読む
目的
protocol buffersでエンコーディングされたバイナリデータを読んでみます。
準備
この記事では、Protocol Buffer Basics: Pythonのコードを使用します。
addressbook.proto
syntax = "proto2"; package tutorial; message Person { required string name = 1; required int32 id = 2; optional string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME]; } repeated PhoneNumber phones = 4; } message AddressBook { repeated Person people = 1; }
writing_a_message.py
#! /usr/bin/python import addressbook_pb2 import sys # This function fills in a Person message based on user input. def PromptForAddress(person): person.id = int(raw_input("Enter person ID number: ")) person.name = raw_input("Enter name: ") email = raw_input("Enter email address (blank for none): ") if email != "": person.email = email while True: number = raw_input("Enter a phone number (or leave blank to finish): ") if number == "": break phone_number = person.phones.add() phone_number.number = number type = raw_input("Is this a mobile, home, or work phone? ") if type == "mobile": phone_number.type = addressbook_pb2.Person.MOBILE elif type == "home": phone_number.type = addressbook_pb2.Person.HOME elif type == "work": phone_number.type = addressbook_pb2.Person.WORK else: print "Unknown phone type; leaving as default value." # Main procedure: Reads the entire address book from a file, # adds one person based on user input, then writes it back out to the same # file. if len(sys.argv) != 2: print "Usage:", sys.argv[0], "ADDRESS_BOOK_FILE" sys.exit(-1) address_book = addressbook_pb2.AddressBook() # Read the existing address book. try: f = open(sys.argv[1], "rb") address_book.ParseFromString(f.read()) f.close() except IOError: print sys.argv[1] + ": Could not open file. Creating a new one." # Add an address. PromptForAddress(address_book.people.add()) # Write the new address book back to disk. f = open(sys.argv[1], "wb") f.write(address_book.SerializeToString()) f.close()
reading_a_message.py
#! /usr/bin/python import addressbook_pb2 import sys # Iterates though all people in the AddressBook and prints info about them. def ListPeople(address_book): for person in address_book.people: print "Person ID:", person.id print " Name:", person.name if person.HasField('email'): print " E-mail address:", person.email for phone_number in person.phones: if phone_number.type == addressbook_pb2.Person.MOBILE: print " Mobile phone #: ", elif phone_number.type == addressbook_pb2.Person.HOME: print " Home phone #: ", elif phone_number.type == addressbook_pb2.Person.WORK: print " Work phone #: ", print phone_number.number # Main procedure: Reads the entire address book from a file and prints all # the information inside. if len(sys.argv) != 2: print "Usage:", sys.argv[0], "ADDRESS_BOOK_FILE" sys.exit(-1) address_book = addressbook_pb2.AddressBook() # Read the existing address book. f = open(sys.argv[1], "rb") address_book.ParseFromString(f.read()) f.close() ListPeople(address_book)
解析
以上のコードよりADDRESS_BOOK_FILEを作成します。
% python reading_a_message.py ADDRESS_BOOK_FILE Person ID: 1 Name: jone E-mail address: jone@example.com Mobile phone #: 000-0000-0001 Person ID: 2 Name: mike E-mail address: mike@example.com Home phone #: 000-0000-0002 Work phone #: 000-0000-0022
ADDRESS_BOOK_FILEをhexdumpすると、以下のようになります。
% hexdump -C ADDRESS_BOOK_FILE 00000000 0a 2d 0a 04 6a 6f 6e 65 10 01 1a 10 6a 6f 6e 65 |.-..jone....jone| 00000010 40 65 78 61 6d 70 6c 65 2e 63 6f 6d 22 11 0a 0d |@example.com"...| 00000020 30 30 30 2d 30 30 30 30 2d 30 30 30 31 10 00 0a |000-0000-0001...| 00000030 40 0a 04 6d 69 6b 65 10 02 1a 10 6d 69 6b 65 40 |@..mike....mike@| 00000040 65 78 61 6d 70 6c 65 2e 63 6f 6d 22 11 0a 0d 30 |example.com"...0| 00000050 30 30 2d 30 30 30 30 2d 30 30 30 32 10 01 22 11 |00-0000-0002..".| 00000060 0a 0d 30 30 30 2d 30 30 30 30 2d 30 30 32 32 10 |..000-0000-0022.| 00000070 02 |.| 00000071
ADDRESS_BOOK_FILEをEncodingに従って解析すると以下のようになります。
アドレス | 00000000 | 00000001 | 00000002 | 00000003 | 00000004 | 00000005 | 00000006 | 00000007 |
---|---|---|---|---|---|---|---|---|
データ | 0a |
2d |
0a |
04 |
6a |
6f |
6e |
65 |
内容 | 0x00001010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0001(people) | payload size (0x2d bytes) (jone分) | 0x00001010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0001(name) | 文字列は4 byte | j | o | n | e |
アドレス | 00000008 | 00000009 | 0000000a | 0000000b | 0000000c | 0000000d | 0000000e | 0000000f |
---|---|---|---|---|---|---|---|---|
データ | 10 |
01 |
1a |
10 |
6a |
6f |
6e |
65 |
内容 | 0x00010000 -> 最後の3ビット(000)よりVarint型, 3回右シフトすると field number = 0x0010(id) | id(jone) = 1 | 0x00011010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0011(email) | 文字列は0x10 byte | j | o | n | e |
アドレス | 00000010 | 00000011 | 00000012 | 00000013 | 00000014 | 00000015 | 00000016 | 00000017 |
---|---|---|---|---|---|---|---|---|
データ | 40 |
65 |
78 |
61 |
6d |
70 |
6c |
65 |
内容 | @ | e | x | a | m | p | l | e |
アドレス | 00000018 | 00000019 | 0000001a | 0000001b | 0000001c | 0000001d | 0000001e | 0000001f |
---|---|---|---|---|---|---|---|---|
データ | 2e |
63 |
6f |
6d |
22 |
11 |
0a |
0d |
内容 | . | c | o | m | 0x00100010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0100(phones) | payload size (0x11 bytes) | 0x00001010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0001(number) | 文字列は0x0d byte |
アドレス | 00000020 | 00000021 | 00000022 | 00000023 | 00000024 | 00000025 | 00000026 | 00000027 |
---|---|---|---|---|---|---|---|---|
データ | 30 |
30 |
30 |
2d |
30 |
30 |
30 |
30 |
内容 | 0 | 0 | 0 | - | 0 | 0 | 0 | 0 |
アドレス | 00000028 | 00000029 | 0000002a | 0000002b | 0000002c | 0000002d | 0000002e | 0000002f |
---|---|---|---|---|---|---|---|---|
データ | 2d |
30 |
30 |
30 |
31 |
10 |
00 |
0a |
内容 | - | 0 | 0 | 0 | 1 | 0x00010000 -> 最後の3ビット(000)よりVarint型, 3回右シフトすると field number = 0x0010(type) | PhoneType = 0(MOBILE) | 0x00001010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0001(people) |
アドレス | 00000030 | 00000031 | 00000032 | 00000033 | 00000034 | 00000035 | 00000036 | 00000037 |
---|---|---|---|---|---|---|---|---|
データ | 40 |
0a |
04 |
6d |
69 |
6b |
65 |
10 |
内容 | payload size (0x40 bytes) (mike分) | 0x00001010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0001(name) | 文字列は4 byte | m | i | k | e | 0x00010000 -> 最後の3ビット(000)よりVarint型, 3回右シフトすると field number = 0x0010(id) |
アドレス | 00000038 | 00000039 | 0000003a | 0000003b | 0000003c | 0000003d | 0000003e | 0000003f |
---|---|---|---|---|---|---|---|---|
データ | 02 |
1a |
10 |
6d |
69 |
6b |
65 |
40 |
内容 | id(mike) = 2 | 0x00011010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0011(email) | 文字列は0x10 byte | m | i | k | e | @ |
アドレス | 00000040 | 00000041 | 00000042 | 00000043 | 00000044 | 00000045 | 00000046 | 00000047 |
---|---|---|---|---|---|---|---|---|
データ | 65 |
78 |
61 |
6d |
70 |
6c |
65 |
2e |
内容 | e | x | a | m | p | l | e | . |
アドレス | 00000048 | 00000049 | 0000004a | 0000004b | 0000004c | 0000004d | 0000004e | 0000004f |
---|---|---|---|---|---|---|---|---|
データ | 63 |
6f |
6d |
22 |
11 |
0a |
0d |
30 |
内容 | c | o | m | 0x00100010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0100(phones) | payload size (0x11 bytes) | 0x00001010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0001(number) | 文字列は0x0d byte | 0 |
アドレス | 00000050 | 00000051 | 00000052 | 00000053 | 00000054 | 00000055 | 00000056 | 00000057 |
---|---|---|---|---|---|---|---|---|
データ | 30 |
30 |
2d |
30 |
30 |
30 |
30 |
2d |
内容 | 0 | 0 | - | 0 | 0 | 0 | 0 | - |
アドレス | 00000058 | 00000059 | 0000005a | 0000005b | 0000005c | 0000005d | 0000005e | 0000005f |
---|---|---|---|---|---|---|---|---|
データ | 30 |
30 |
30 |
32 |
10 |
01 |
22 |
11 |
内容 | 0 | 0 | 0 | 2 | 0x00010000 -> 最後の3ビット(000)よりVarint型, 3回右シフトすると field number = 0x0010(type) | PhoneType = 1(HOME) | 0x00100010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0100(phones) | payload size (0x11 bytes) |
アドレス | 00000060 | 00000061 | 00000062 | 00000063 | 00000064 | 00000065 | 00000066 | 00000067 |
---|---|---|---|---|---|---|---|---|
データ | 0a |
0d |
30 |
30 |
30 |
2d |
30 |
30 |
内容 | 0x00001010 -> 最後の3ビット(010)よりLength-delimited型, 3回右シフトすると field number = 0x0001(number) | 文字列は0x0d byte | 0 | 0 | 0 | - | 0 | 0 |
アドレス | 00000068 | 00000069 | 0000006a | 0000006b | 0000006c | 0000006d | 0000006e | 0000006f |
---|---|---|---|---|---|---|---|---|
データ | 30 |
30 |
2d |
30 |
30 |
32 |
32 |
10 |
内容 | 0 | 0 | - | 0 | 0 | 2 | 2 | 0x00010000 -> 最後の3ビット(000)よりVarint型, 3回右シフトすると field number = 0x0010(type) |
アドレス | 00000070 |
---|---|
データ | 02 |
内容 | PhoneType = 2(WORK) |
バイナリって楽しいですね。
github-trending を公開しました。[Command Line Tool]
コマンドラインツール「github-trending」を公開しました。
What is this?
コマンドラインでTrending repositories on GitHub today · GitHubを眺めるツールです。
github.com
機能は以下の2点です。
- Trending repositories on GitHub today · GitHubを眺める
- リポジトリのREADMEを眺める
使い方
Syntax
Usage:
$ gt <command> [params] [options]
Auto-Completer and Interactive Help
以下のコマンドにより、フィッシュスタイルの補完とインタラクティブヘルプを使用した自動補完メニューを有効にできます。
$ github-trending
オートコンプリータ内では、同じ構文が適用できます。
github> gt <command> [params] [options]
View Trending
Trending repositories on GitHub today · GitHubを眺めます。
Usage:
$ gt trend [option] [limit]
Examples:
$ gt trend $ gt trend --language python $ gt trend --dev $ gt trend --monthly $ gt trend --limit 10
インストール
本ツールはgithub-trending-cli · PyPIで公開しております。
次のコマンドでインストールできます。
$ pip install github-trending-cli
終わりに
本ツールはOSSとして、Apache licenseで公開しております。(PR絶賛受け付けております!)
github.com
また、使い方の詳細はREADMEに書いております。
是非、ご利用ください。
Atom UI themeを公開しました
「seti-black-ui」というAtom UI themeを公開しました。
動機
元々「seti-ui」というthemeを使っていましたが、自分好みに改良を加えているうちに、オリジナルとは異なるものになっていったので、公開することにしました。(seti-uiの作者Jesse Weed氏に謝意を表す)
ただ、あくまでもオレオレテーマなので、自分のためだけに改良して、呑気にリリースする予定です。
syntax themeも近い内に公開する予定です。
興味がある方は是非ご利用ください。
Chrome拡張機能「その本、メルカリで!」を公開しました
Chrome拡張機能「その本、メルカリで!」を公開しました。
メリット
・amazonでの値段とメルカリでの値段が比較できます(中古本はメルカリの方が安い場合が多い)
・ワンクリックでメルカリの検索ページに飛べます
以下で公開しているので、興味がある方は是非ご利用ください。
chrome.google.com
ダイクストラ法(C言語)
ダイクストラ法について調べてみると、思った以上に解説が少ない。それでもアルゴリズムを理解する分には、充分にある。
しかし、実装については別だ。
「ダイクストラ法 C言語」でググって表示されるサイトに載っているコードはまともに動かない。
だから、C言語でダイクストラ法を書くことにした。
アルゴリズムの解説では
http://nw.tsuda.ac.jp/lec/dijkstra/
がとても素晴らしい。大変参考になりました。ありがとうございました。
しかし、このサイトではPythonとJavaでの実装のみ。加えて、優先順位付きキューを使用している。C言語には、そんな高級なデータ構造はないので、普通に配列を使って実装します。
以下がそのコード。(コメントは参考サイトのアルゴリズム解説の文を用いました && コメントは変数名は冗長に書きました)
#include <stdio.h> #define INF 10000000 #define SIZE 1000 #define TRUE 1 #define FALSE 0 int DIST[SIZE][SIZE]; int COST[SIZE]; int VIA[SIZE]; int N; char USED[SIZE]; int dijkstra(int start, int goal) { int min, target; COST[start] = 0; while(1){ /* 未確定の中から距離が最も小さい地点(a)を選んで、その距離を その地点の最小距離として確定します */ min = INF; for(int i = 0; i < N; i++){ if(!USED[i] && min > COST[i]) { min = COST[i]; target = i; } } /* 全ての地点の最短経路が確定 */ if(target == goal) return COST[goal]; /* 今確定した場所から「直接つながっている」かつ「未確定の」地点に関して、 今確定した場所を経由した場合の距離を計算し、今までの距離よりも小さければ書き直します。 */ for(int neighboring = 0; neighboring < N; neighboring++){ if(COST[neighboring] > DIST[target][neighboring] + COST[target]) { COST[neighboring] = DIST[target][neighboring] + COST[target]; VIA[neighboring] = target; } } USED[target] = TRUE; } } int main(void){ int r; int a,b,l; int s,d; /* 初期化 */ for(int i = 0; i < SIZE; i++) { COST[i] = INF; USED[i] = FALSE; VIA[i] = -1; for(int j = 0; j < SIZE; j++) DIST[i][j] = INF; } /* 入力 */ scanf("%d %d", &N, &r); for(int i = 0; i < r; i++){ scanf("%d %d %d", &a, &b, &l); DIST[a][b] = l; } scanf("%d %d", &s, &d); /* ダイクストラ法で最短経路を求める */ printf("distance:%d\n", dijkstra(s,d)); /* 経路を表示(ゴールから) */ int node = d; printf("%d", node); while(1){ node = VIA[node]; printf(" -> %d", node); if (node == s) break; } return 0; }
実行結果。
% ./dijkstra 7 10 0 1 30 0 3 10 0 2 15 1 3 25 1 4 60 2 3 40 2 5 20 3 6 35 4 6 20 5 6 30 0 6 distance:45 6 -> 3 -> 0
データは参考サイトの項「課題2」にあります。
2017を振り返る
年末バタバタで今年を振り返るのは無理そうだと思ったが、今年も残り2時間というところで、急に手持ち無沙汰になったので簡単に振り返る。
1月~3月
CTF4bに行ったことをきっかけにCTFを始めた。Pwnを中心にやっていたこともあり、低レイヤの魅力にハマって、色々な文献に目を通して、世界が広がった。勉強会にも参加して、人脈を作れたことも良かった。
大学卒業した。みんな4年間ありがとう!
5月
逆求人イベントに行って、色々な会社と接することができた。そこで、やっぱりエンジニアではなく、シンクタンク・コンサル行こうと思い、シンクタンク・コンサルのインターンを探し始めた。
SecHackが合格になった。技術やるならセキュリティがいいなと思っていたので、嬉しかった。受かったからには、頑張らないとなという思いから、苦手なネットワークの勉強をした。(結局、一年取り組むことになる)
6月、7月
インターン応募のためにES書きまくって面接しまくった。コンサルのための本とかたくさん読んで、ESの書き方、面接での答え方を勉強して、実践、反省を繰り返した。最初の頃は下手くそだったけど、途中からは受かりまくったので、やり方は間違ってなかったらしい。この辺から秋までは技術の勉強は対してやらなかったので、エンジニアとしての成長はあまりない。
8月
旅に行けた。絶景最高。
9月
2社インターン。すごく優秀な社員さんと学生に囲まれて幸せだった。結果はボロボロだったけど、コンサルの思考・仕事術を少しは盗めたことは大きい。仮説思考・アウトプットドリンに触れることができて良かった。
10月
北海道寒かったな。
冬インターンのためにまたESを書き始めた。
11月
祖父が亡くなった。色々考えた。
12月
インターン。相変わらずアウトプットドリブン。金融業界に興味を持ち始めた。
総まとめ
コツコツとネットワークの勉強を続けて、だいぶ理解が深まった。一番苦手な分野が苦手でなくなったことは大きい。
面接をたくさんこなして、面接が得意になった。
コンサルの仕事が出来て良かった。コンサルの仕事術はどの仕事をしても役に立つ。
6月から忙しくなって、4つくらいプロジェクトは回すわ、各地に飛ばなくてはならないわで、途中体調も崩したし、疲れた。しかし、精神的充実は大きい。それらのプロジェクトは今も続いてるわけだけど、全体的にパフォーマンスが悪い。一つ一つの完成度は8割くらいでイマイチだと思ってる。来年はマルチタスクをこなせるようになりたい。(そのためには何をすべきなのか?)
来年やりたいこと
「書く」ことに時間を割きたい。
47都道府県制覇したい。(現在40都道府県)
2月から圧倒的当事者意識の会社で働くので、気を引き締めて頑張りたい。
あっ、音楽では渋谷系にハマった。
オザケン、スカパラ、ピチカート・ファイヴ。一年ずっと聞いてたな。あと、The Strokes。
皆様一年ありがとうございました!
来年も是非よろしくお願いいたします!!
ELFとELFローダについて
ROPというハッキング手法がある。これを勉強していたら、共有ライブラリがメモリ上のどこに配置されるのか、それはどのタイミングなのか気になり始めた。そして、色々と文献を漁ってぼんやりとしたことは分かってきたが、知識が定着したとは言えないので、自分の理解をより深めるため、知識を定着させるため以下に、ELF周りの話を記す。
$uname -a Linux VM 3.13.0-24-generic #47-Ubuntu SMP Fri May 2 23:30:00 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux $lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 14.04.5 LTS Release: 14.04 Codename: trusty $ gcc --version gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4 // ASLR無効化 $ sudo sysctl -w kernel.randomize_va_space=0 kernel.randomize_va_space = 0
hello.c
#include <stdio.h> int main(void) { printf("Hello, World!\n"); return 0; }
ELF(Executable and Linking Format)
ELFとは、実行可能バイナリやオブジェクトファイルなどのフォーマットを規定したもの。ELFフォーマットのファイルは、ELFヘッダが先頭にあり、プログラムヘッダテーブル及びセクションテーブルがその後にあります。これらのヘッダ構造はelf.hに記述されている。
// http://lxr.free-electrons.com/source/tools/objtool/elf.h#L69 struct elf { Elf *elf; GElf_Ehdr ehdr; int fd; char *name; struct list_head sections; DECLARE_HASHTABLE(rela_hash, 16); };
ELFヘッダ
ELFヘッダはELFファイルの先頭に必ず存在し、そのファイルがELFファイルであることを示す。
ELFヘッダはelf->ehdrのことで以下のように定義されている。
// http://lxr.free-electrons.com/source/include/uapi/linux/elf.h#L235 typedef struct elf64_hdr { unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */ Elf64_Half e_type; Elf64_Half e_machine; Elf64_Word e_version; Elf64_Addr e_entry; /* Entry point virtual address */ Elf64_Off e_phoff; /* Program header table file offset */ Elf64_Off e_shoff; /* Section header table file offset */ Elf64_Word e_flags; Elf64_Half e_ehsize; Elf64_Half e_phentsize; Elf64_Half e_phnum; Elf64_Half e_shentsize; Elf64_Half e_shnum; Elf64_Half e_shstrndx; } Elf64_Ehdr;
$ readelf -h a.out ELF ヘッダ: マジック: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 クラス: ELF64 データ: 2 の補数、リトルエンディアン バージョン: 1 (current) OS/ABI: UNIX - System V ABI バージョン: 0 型: EXEC (実行可能ファイル) マシン: Advanced Micro Devices X86-64 バージョン: 0x1 エントリポイントアドレス: 0x400440 プログラムの開始ヘッダ: 64 (バイト) セクションヘッダ始点: 4472 (バイト) フラグ: 0x0 このヘッダのサイズ: 64 (バイト) プログラムヘッダサイズ: 56 (バイト) プログラムヘッダ数: 9 セクションヘッダ: 64 (バイト) セクションヘッダサイズ: 30 セクションヘッダ文字列表索引: 27
プログラムヘッダ
プログラムヘッダテーブルはELFヘッダのe_phoffで指定されるオフセットから始まり、e_phentsizeとe_phnumで決まる大きさのテーブルからなります。e_phentsizeがテーブルの中のプログラムヘッダのサイズを表し、e_phnumがそのテーブルの中にいくつセッションヘッダがあるか示しています。
$ readelf -l a.out Elf ファイルタイプは EXEC (実行可能ファイル) です エントリポイント 0x400440 9 個のプログラムヘッダ、始点オフセット 64 プログラムヘッダ: タイプ オフセット 仮想Addr 物理Addr ファイルサイズ メモリサイズ フラグ 整列 PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040 0x00000000000001f8 0x00000000000001f8 R E 8 INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238 0x000000000000001c 0x000000000000001c R 1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x000000000000070c 0x000000000000070c R E 200000 LOAD 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10 0x0000000000000230 0x0000000000000238 RW 200000 DYNAMIC 0x0000000000000e28 0x0000000000600e28 0x0000000000600e28 0x00000000000001d0 0x00000000000001d0 RW 8 NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254 0x0000000000000044 0x0000000000000044 R 4 GNU_EH_FRAME 0x00000000000005e4 0x00000000004005e4 0x00000000004005e4 0x0000000000000034 0x0000000000000034 R 4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 10 GNU_RELRO 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10 0x00000000000001f0 0x00000000000001f0 R 1 セグメントマッピングへのセクション: セグメントセクション... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.ABI-tag .note.gnu.build-id 06 .eh_frame_hdr 07 08 .init_array .fini_array .jcr .dynamic .got
ここでなぜ0x0000000000400000にELFファイルが配置されているか知りたい場合は
ELF実行ファイルのメモリ配置はどのように決まるのか - ももいろテクノロジー
を参照すると良い。
INTERPとは動的リンクを実際に処理するインタプリタ(リンカ)のことである。
http://lxr.free-electrons.com/source/fs/binfmt_elf.c?v=3.2#L559にELFファイルをメモリにロードする部分がある(多分)。
そこを読み解く前に、linux_binprm 型という、構造体の説明。これは、ユーザー空間にある引数をカーネル側で保持・加工する為のデータ構造らしい。
// http://lxr.free-electrons.com/source/include/linux/binfmts.h?v=3.2#L28 /* * This structure is used to hold the arguments that are used when loading binaries. */ struct linux_binprm { char buf[BINPRM_BUF_SIZE]; #ifdef CONFIG_MMU struct vm_area_struct *vma; unsigned long vma_pages; #else # define MAX_ARG_PAGES 32 struct page *page[MAX_ARG_PAGES]; #endif struct mm_struct *mm; unsigned long p; /* current top of mem */ unsigned int cred_prepared:1,/* true if creds already prepared (multiple * preps happen for interpreters) */ cap_effective:1;/* true if has elevated effective capabilities, * false if not; except for init which inherits * its parent's caps anyway */ #ifdef __alpha__ unsigned int taso:1; #endif unsigned int recursion_depth; struct file * file; struct cred *cred; /* new credentials * / int unsafe; /* how unsafe this exec is (mask of LSM_UNSAFE_*) */ unsigned int per_clear; /* bits to clear in current->personality */ int argc, envc; const char * filename; /* Name of binary as seen by procps */ const char * interp; /* Name of the binary really executed. Most of the time same as filename, but could be different for binfmt_{misc,script} */ unsigned interp_flags; unsigned interp_data; unsigned long loader, exec; };
はじめに、ELFヘッダのサイズ分をmallocして、ELFヘッダを読み込み、各要素をチェックする。次に、全てのプログラムヘッダのサイズ分をmallocして、全てプログラムヘッダを読み込む。そして、それを順にチェックしていき、p_type(セグメンタイプ)がINTERPの場合、その内容を読み取る。つまり、elf_interpreter="/lib64/ld-linux-x86-64.so.2"。ここでは、open_exec()した後、interpreterのBINPRM_BUF_SIZE分(128byte)をloc->interp_elf_exに代入。つまりはinterpreterの情報。
次にp_typeがPT_GNU_STACKセクションの場合、ELFヘッダのp_flagsをチェックしてスタック上でのコード実行の可否を決める。(ここはDEPと関わってくる)。
次に、INTERPの妥当性(マジックナンバーと、アーキテクチャの適合性)をチェック。
flush_old_exec()で現在のprogramの情報を消し、新しいprogramのための情報にcurrentの情報をいれかえていく。current は、カーネル中で現在実行中のプロセスの task_struct 構造体 を保持する変数。(プロセス、リンク、task_struct構造体)
SET_PERSONALITY()で親のpersonalityを継承する。その後、currentの情報を更新していく。(ここはlinuxのプロセス管理のお話なので、いつか勉強したい)
次はELFをmmapingする。p_typeがPT_LOADの場合にp_flagsを調べ、パーミッションを設定する。ヒープセクションがbssセクションより下位アドレスにあった場合、対象領域をクリアする?
その後、 MAP_PRIVATEとe_typeがET_EXECであった場合ET_MAP_FIXEDをelf_flagsに設定。(https://linuxjm.osdn.jp/html/LDP_man-pages/man2/mmap.2.html/)
共有オブジェクトならランダムフラグを見てアドレス計算。
オフセットとか計算したら、elf_map(おそらく中身はmmap()、セマフォとかしてるし、後々解析する予定)する。
mmap()した先のアドレス、セクションのサイズ等をチェックする。
その後、ヒープセクション、bssセクションの調整?(よくわかっていない)
load_elf_interp()でinterpreterを読み込む。読み込もうとしているinterpreterのe_typeがET_EXECかET_DYN(共有ライブラリ)なのか、アーキテクチャは合っているか、interpreterのfile operation等をチェック。e_phentsizeとe_phnumでサイズチェック。
interpreterのサイズ分のメモリを確保して、kernel_read()する。その後、elf_map()で共有ライブラリをmappingする。
その後、読み込んだアドレス等をチェック。そして、current情報を更新。
453 map_addr = elf_map(interpreter, load_addr + vaddr, 454 eppnt, elf_prot, elf_type, total_size); 462 if (!load_addr_set && 463 interp_elf_ex->e_type == ET_DYN) { 464 load_addr = map_addr - ELF_PAGESTART(vaddr); 465 load_addr_set = 1; 466 } 523 error = load_addr; 528 return error;
そして、start_thread(regs, elf_entry, bprm->p)で先ほどのerrorがeipに設定される。
この呼び出し元に戻ると、どうやらeipから実行するらしく、すなわち共有ライブラリが実行されることになる。
感想
OSのメモリマッピングの知識が欠けていたので、mmap()周辺が正しく解析できていない。よって、共有ライブラリの読み込み先アドレス等の計算が未だに分かっていないため、目的は達成できていない(参考文献読んだら理解できた)。メモリマッピングの勉強をしたら、また読んでみよう。
linux kernelのソースコードを初めて読んだ。面白くて仕方なかった。しかし、特有のデータ構造や頻繁に使われる関数等を一々追っていたため、記事の内容もまとまらず、知識も断片的なものとなった。これから他のところを読んだ時に、今回の知識は役に立つのであろうか?
もしそうでない場合は、また色々と追っているうちに闇に吸い込まれていくわけであるが、いつかは塵も積もり山となるであろう。
参考文献
Linux/ - Linux Cross Reference - Free Electrons
Linux 動的ライブラリーの徹底調査
プログラムはどう動くのか? 〜 ELFの黒魔術をかいまみる
linuxの話 (ELF ローダ) + 他:俺のインターフェイス:So-netブログ
ELF Format
ELFの動的リンク(1) - 七誌の開発日記
ELF実行ファイルのメモリ配置はどのように決まるのか - ももいろテクノロジー
プロセス、リンク、task_struct構造体
メモリ管理、アドレス空間、ページテーブル
ld.so - システム管理コマンドの説明 - Linux コマンド集 一覧表
http://www.skyfree.org/linux/references/ELF_Format.pdf
Binary Hacks ―ハッカー秘伝のテクニック100選