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」を公開しました。

https://i.imgur.com/Jh0Ddmx.gif

What is this?

コマンドラインTrending repositories on GitHub today · GitHubを眺めるツールです。
github.com

機能は以下の2点です。

使い方

Syntax

Usage:

    $ gt <command> [params] [options]

Auto-Completer and Interactive Help

以下のコマンドにより、フィッシュスタイルの補完とインタラクティブヘルプを使用した自動補完メニューを有効にできます。

    $ github-trending

オートコンプリータ内では、同じ構文が適用できます。

    github> gt <command> [params] [options]

https://i.imgur.com/fwHg07X.png

Commands

https://i.imgur.com/eer1XsJ.png

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

https://i.imgur.com/5Bxmdld.png

View Repository README

リポジトリのREADMEを眺めます。
Usage:

    $ gt view [user/repository]

https://i.imgur.com/Liq7Wvq.png
https://i.imgur.com/VYLklBq.png

インストール

本ツールはgithub-trending-cli · PyPIで公開しております。
次のコマンドでインストールできます。

    $ pip install github-trending-cli

サポートPythonバージョン

サポートプラットフォーム

終わりに

本ツールはOSSとして、Apache licenseで公開しております。(PR絶賛受け付けております!)
github.com

また、使い方の詳細はREADMEに書いております。

是非、ご利用ください。

Atom UI themeを公開しました

seti-black-ui」というAtom UI themeを公開しました。

f:id:blue_9:20180621002648p:plain
seti-black-ui

特徴

・黒いです
・ミニマルなデザインです

atom.io

動機

元々「seti-ui」というthemeを使っていましたが、自分好みに改良を加えているうちに、オリジナルとは異なるものになっていったので、公開することにしました。(seti-uiの作者Jesse Weed氏に謝意を表す)
ただ、あくまでもオレオレテーマなので、自分のためだけに改良して、呑気にリリースする予定です。
syntax themeも近い内に公開する予定です。

興味がある方は是非ご利用ください。

Chrome拡張機能「その本、メルカリで!」を公開しました

Chrome拡張機能その本、メルカリで!」を公開しました。

What is it?

amazonで見ている本が、メルカリで何円なのか分かります。

f:id:blue_9:20180610201721p:plain
amazonページに、メルカリでの値段が表示されます

f:id:blue_9:20180610201725p:plain
クリックするとメルカリの検索ページに飛びます

メリット

amazonでの値段とメルカリでの値段が比較できます(中古本はメルカリの方が安い場合が多い)
・ワンクリックでメルカリの検索ページに飛べます


以下で公開しているので、興味がある方は是非ご利用ください。
chrome.google.com

ダイクストラ法(C言語)

ダイクストラ法について調べてみると、思った以上に解説が少ない。それでもアルゴリズムを理解する分には、充分にある。
しかし、実装については別だ。
ダイクストラ法 C言語」でググって表示されるサイトに載っているコードはまともに動かない。
だから、C言語ダイクストラ法を書くことにした。


アルゴリズムの解説では
http://nw.tsuda.ac.jp/lec/dijkstra/
がとても素晴らしい。大変参考になりました。ありがとうございました。
しかし、このサイトではPythonJavaでの実装のみ。加えて、優先順位付きキューを使用している。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のソースコードを初めて読んだ。面白くて仕方なかった。しかし、特有のデータ構造や頻繁に使われる関数等を一々追っていたため、記事の内容もまとまらず、知識も断片的なものとなった。これから他のところを読んだ時に、今回の知識は役に立つのであろうか?
もしそうでない場合は、また色々と追っているうちに闇に吸い込まれていくわけであるが、いつかは塵も積もり山となるであろう。