「 2013年12月 」一覧


Windowsファイル共有でのGitリポジトリ運用

前回 QNAP のファイルサーバーで Git リポジトリを扱おうとして、思うようにいかなかったので他の方法を探してみました。
ちなみに、あれから git daemon による git リポジトリを扱う方法もやってみたのですが、Windowsのmsysgitのgitからは、Write Objects の状態から進まずという(フリーズ?)という状態で停止でした。

どうしてもファイルサーバー(NAS)上にリポジトリはおいておきたいので、方法を探した結果、Windowsのファイル共有でリポジトリを置く方法にトライしてみました。

構成

  • NASはWindowsファイル共有でリポジトリ用フォルダを公開
  • 他のWindows PCは上記のフォルダへアクセス可能。TortoiseGit を使用する

共有フォルダにリポジトリを作成

リポジトリを置く共有フォルダ(NAS上)で、リポジトリのフォルダを作成します。ここでは hello.git フォルダを作成しました。そして、そのフォルダを開いて、右クリックしてここにリポジトリを作成を選び、リポジトリ作成(bare)をします。
init-bare

リポジトリをクローンする

使用するPCで、上記のリポジトリからクローンします。ここでもまたTortoiseGitを使用しているとして、Gitクローンメニューを選択します。そして、URLでは先ほどの共有リポジトリのWindowsファイルパスを入力します。

例:\\FileServerName\repos_git\hello.git

今は初回のリポジトリ(ベアリポジトリ)のクローンのため、ワーニングが出ますが気にしないでおきます。

共有リポジトリへプッシュ(Push)

クローンしたフォルダで適当にファイルを追加して、Push までを確認してみたいと思います。
そのままPushを実行して問題ないはずです。もし失敗するようであればリモートの確認をしてみて下さい。

宛先のリモートが origin になっていると思いますが、その横の管理ボタンを押して、Git/リモートの項目内で origin を選択してみると情報が表示されます。このとき、URLに指定されているパスが Windowsファイル共有で指定したものと同じになっているかどうかを確認してみてください。またそのパスが有効な物であるか確認してみて下さい。

まとめ

Windowsだけで、しかもローカルで閉じているような環境の場合、このリポジトリ管理が一番楽なのかもという感想になりました。Web等で公開するリポジトリではこれはいけませんが、編集するマシンとしてデスクトップとノートPCがあり、さらにNASも運用している、という自宅環境ではアリな気がしています。


QNAPファイルサーバー(NAS)で、Gitを使いたい

QNAPのファイルサーバー(NAS)にSubversionは以前いれてみたので、Gitも同様にできないかと考えていました。できればGitLabとか入れてみたいなと思っていたのですが手持ちの TS-439では厳しいようだったので、他の物を探してみました。
その結果、warファイル1つで簡単にGitリポジトリのホスティングをできるGitBucketがあったので、これを導入してみることにしました。以下はその導入記録です。

QNAPにtomcatを入れる

QNAP/AppCenter(QPKG) のページからJREをダウンロードしてPCに展開します。
そして、展開フォルダを指定してインストールを行います。
続いて Tomcat についても同様の手順でインストールを行います。
(ここでは、JRE_6.0.22_x86.qpkg, Tomcat_6.0.20_x86.qpkg を使用しました)

インストール後は Tomcat を起動して、画面を開いてみます。

qnap-tomcat

tomcat のアプリケーションを配置するフォルダは
/share/MD0_DATA/.qpkg/Tomcat/tomcat/webapps
になる模様。
$CATALINA_HOME は /share/MD0_DATA/.qpkg/Tomcat/tomcat ということのようです。
そこで、$CATALINA_HOME/conf/tomcat-users.xml を開いてユーザーを編集します。

※ ここ見れば既存のユーザーとパスワードの組み合わせもわかると思います。

そして、Tomcatの管理画面にログインすると次のように表示されます。

tomcat-manager

GitBucketのインストール

https://github.com/takezoe/gitbucket/releases
から gitbucket.war をダウンロードして、これを先ほどのTomcat管理画面からアップロードします。ここで使用した gitbucket.war は 1.8 のバージョンとなっています。

さて、GitBucket を Start を押すと・・・起動しません。
ログを確認してみると、クラスが足りていないような感じです。
javaが古いのかと思って調べてみたのですがどうやら Tomcat のほうが古いようです。qpkgでインストールされるのは 6.0系なようで、GitBucketは 7.0 以降のものを必要としているようです。残念。

というわけで、Tomcatの新しい物が利用可能になれば使えるのかもな~と思っている次第です。


elfバイナリでの関数フック. 強引編

今までのものは手間はかかっているもののきちんと手順を踏んで、わりと行儀よく(?)関数のフックを実現していました。今回は豪快な関数フックの方法を試してみたいと思います。

※ なおこの手順で問題が起こっても自己責任でお願いします。前提条件として 32bitのアプリケーションとしています。

その方法とは、対象関数の先頭に、自分のフック関数へのジャンプを埋め込んでしまえばいい!というものです。
具体的には、関数先頭のアセンブラを書き換えて、自分の用意したフック関数のアドレスへの無条件ジャンプに数値を書き換えてしまう、という流れになります。

多くの場合、関数の配置されているメモリは実行可能&リード可能なメモリ領域としてマークされています。そのため、まずはメモリ領域の属性を変更する必要があります。Linuxにおいては下記のプログラムのようにして属性を変更します。

ここで func が目的の関数となります。関数アドレスから、その関数が所属しているページを割り出して mprotect API で属性を変更します。

その後、関数のアドレスの場所に 5バイトほど値を書き込みます。

0xE9 [フック関数のアドレス] で5バイトです。これはアセンブラではアドレスへの無条件ジャンプとなります。

これらの手順で加工した元の関数を呼び出すと、見事に hookedFunc に実行が移ってきていると思います。今回はいきなり5バイト破壊してしまっているので、フック関数から、元の関数を呼び出すような実装となっていません。必要ならば、このあたり整合性を持つようにバイト列をコピー&加工する必要があると思います。元に戻す場合には書き換えた5バイトをバックアップから書き戻すようにすればできるでしょう。


セクションヘッダがない場合の関数フック(elf編)

前回は共有ライブラリ(.so)での関数フックの話でした。そしてこれはセクションヘッダがある前提で rel.plt セクションを見つけました。今回はセクションヘッダを参照しないでPLTの場所を特定し、関数フックを実現する案を記載してみようと思います。

そもそも elf実行体がメモリにロードされて実行されている状況では、elf実行体そのものにセクションヘッダがあったとしてもメモリにロードされていません。実行体そのもののファイルを読み込んでセクションヘッダを解析して特定する方法もあるかと思います。前回のsoの場合においては、そのようにして手元ではサンプルを組んでいました。しかし、メモリにロードされている情報からうまく特定できるのであれば無駄なメモリを使うことなく、ディスクIOも減らせて何かと都合がよいです。またストリップ化されているsoについてはセクションヘッダなしで何とか特定しなければなりません。

つまり、セクションヘッダなしで前回と同じようなことを実現するのを目的としたいと思います。今回は下記のような呼び出しをフックする方向を考えています。
elf-hook1

elf実行体からある共有ライブラリの関数を呼び出しており、その関数のフック先は別に自分自身の内部に用意してあるというものです。フック処理を適用して呼び出し先を変更します。

続きを読む


C++/CLIで妙なリンクエラー(LNK2034)

VisualStudio 2012の環境で、C++/CLI でのDLLを作成していたところ、妙なリンクエラーに出逢いました。このDLLは他のC/C++のライブラリをリンクして、C#から扱うためのラッパーとして機能するものです。以前は VisualStudio 2008 の環境でコンパイルさせていました。

続きを読む


共有ライブラリ(.so) の関数呼び先をフック

前回の内容でとりあえずシンボル情報にアクセスする糸口がわかりました。
これを利用して、共有ライブラリの関数フックを考えてみたいと思います。

sample.app が共有ライブラリ libfunc.so と libbar.so を利用しているとします。そしてこれらが下記の図に示すような呼び出し関係を持っているとします。

original_call

しかし、このlibbar.so の関数にはバグがあるようで、しかも今すぐには修正できないような状況だったとします。自分ではなく他の人の作成したライブラリを使うような場合、こういうことってありますよね・・・。
 そこで、関数そのものを一時的であれ自分ハックをいれたようなものに置き換えて今をしのぐ、というようなことを考えます。ここで関数フックの登場となります。そして下記に示すような呼び出し関係を構築することを目標とします。

modified_call

前回の内容と対象のsoがロードされているベースアドレスから、soがもっているセクションヘッダを取得します。そしてそのセクションヘッダを解釈して、置き換えたい関数のPLTの場所を特定します。PLTの場所が判明し、そこの内容をフック先の関数アドレスを入れておけば、この図のような呼び出しのリダイレクトが実現できます。

※ ひとまず 32bit 実行環境であると想定して話を進めます。
また、共有.soについては PIC で生成されているものとします。

やり方としては、まず “.rel.plt”セクションのセクションヘッダを取得します。
このヘッダの sh_addr と 共有ライブラリのベースアドレスを加算したアドレスに、 Elf32_Rel構造体の情報が格納されています。個数は sh_size がバイトサイズなので、1要素単位で除算して求めておきます。
Elf32_Rel.r_info メンバを ELF32_R_SYM マクロを使って、シンボルのインデックスを取得することができます。シンボルのインデックスは Elf32_Sym 配列の序数として使えるので、関連するストリングテーブルを取得しておけば関数の名前が求まります。関数の名前を比較して、フックしたい該当関数かどうかを判定します。
 続いて、PLTの場所ですがこれは Elf32_Rel.r_offsetメンバを参照することでわかります。これもまたベースアドレスからのオフセットになっています。これでアドレスが特定できます。実際にそのアドレスの中身を見ると関数先頭のアドレスが格納されています。動的リンクではこのテーブルのアドレスを見て関数ジャンプという仕組みでおこなっているため、このアドレスを自分の関数をいれておけばOKという仕掛けでフックを実現します。

文章ではちょっとわかりにくさを感じたので、これらの位置関係を図示してみました。こんな感じです。
pos_rel_plt

通常PLT経由のジャンプにおいては、PLTの該当場所に記録されたアドレスを参照して jmp命令を実行するというコードになっています。初期状態ではこのPLTには jmp命令の次の行を示しており、そこから実際の関数はどこなのかを調査するコードが実行されます。このコードが実行されると、PLTの場所に見つけ出された関数のアドレスが記録されます。次回以降はこの調査コードが実行されずに関数を呼び出すことができるという仕掛けになっています。

このPLTジャンプの内容については、ここ(共有ライブラリーはどのように動作するか)がわかりやすいかも。

これが動作する秘密は、PLT(Procedure Linkage Table)と呼ばれるデータの塊、つまりプログラムが呼ぶ全てのファンクションをリストアップした、プログラム中のテーブルです。プログラムが開始する時に、ファンクションがロードされたアドレスをランタイム・リンカーに対して問い合わせるコードを、PLTは各ファンクションに対して持っています。そうするとプログラムは、テーブル中のそのエントリーを取り込み、そこにジャンプします。それぞれのファンクションが呼ばれるにつれ、それぞれのファンクションに対するPLTでのエントリーは、ロードされたファンクションへの直接ジャンプに単純化されます。

余談

関数フックについては、同じ関数名で実装した別の .so を作成し、これをLD_PRELOAD環境変数にセットして、プログラムを実行するという方法があります。Linuxでは割と定番の関数フックです。今回のような内容は、LD_PRELOADを使えば簡単にできるのですが、アプリを使う側への手間をかけたくない、コンパイル(&リンク)時に全ての解決をしてしまいたい、というポリシーのため調べてみたという経緯です。


Elfフォーマットのシンボル情報を取得したい

gnuのツールでは、objdump や readelf, nm といったツールで実行体の情報を出力することができます。これはElfバイナリにセクションやシンボルの情報がどのように格納されているかがセクションヘッダとして格納されているために表示できます。
そのため、stripコマンドでセクションヘッダを削除してしまうと、これらの情報が表示されなくなってしまいます。
またセクションヘッダはElfバイナリの末尾についているため、そもそも削除されることを想定しています。このセクションヘッダはツールによる解釈のために存在するようで、実行時には不要な物です。そのためローダーはこの部分をロードしません。(正確にはストリングテーブルからロードしないようだ…)

しかしここではこのセクションヘッダが存在するとして、話を進めたいと思います。
セクションヘッダは Elf32_Shdr の配列として存在しています。これを読み込んでいくと、sh_typeメンバの値が SHT_DYNSYM というものが見つかります。そしてこのセクションの内容(sh_addrで示される場所において)が、Elf32_Symの配列となっています。
このとき、セクションヘッダに示されている sh_linkのセクションヘッダを参照して文字列が格納されているテーブルも取得しておきます。名前はこのテーブル先頭からのオフセットで格納されているためです。

  1. セクションヘッダを読み込む
  2. 読み込んだセクションヘッダのリストから、SHT_DYNSYM, SHT_SYMTAB のものを抽出
  3. SHT_SYMTABのsh_addrによりストリングテーブルを取得
  4. SHT_DYNSYMの中身は Elf32_Symの配列.
  5. ストリングテーブルのアドレス+Elf32_Sym.sh_name で文字列を得る

このような手順で、含まれているシンボル情報を見ることができます。

冒頭で述べた、セクションヘッダを削除すると情報がみれない話ですが、バイナリエディタで見るとストリングテーブルは残っていたりします。実行にはこのテーブルもロードされないので削除してしまっても正常に動きます。実行体elfになるべく情報を残さないというポリシーならばこのストリングテーブルの削除も忘れずにやっておきたいところです。


gccによるC/C++ランタイムのスタティックリンク

前回興味を持った内容の続きです。CランタイムやC++ランタイムをgccを使える環境でスタティックリンクで実行バイナリにリンクしてしまう!という話になります。

32bit,64bitビルドの区別

gccでコンパイルする際には、-m32, -m64 のオプションで生成するElfバイナリの形式を決めるようです。それぞれが32bit(x86)用コード生成、64bit(x86_64)用コード生成となります。このオプションを使わない場合、gccの実行環境で生成物が変わるようです(64bit Linux上なら64bit elfというように)。

準備

スタティックリンクをやろうとした際には、そもそもスタティックリンクのライブラリが存在しないといけないです。そのライブラリをインストールする必要があり、Ubuntuでは、下記のようにしてインストールしました。

sudo apt-get install build-essential g++-4.6-multilib lib32stdc++6

他の環境では、glibc-static というパッケージをインストールして準備完了というものもあるようです。
これらを忘れるとライブラリが見つからない!とエラーで悩むことになります。

C++

次のようなコードをサンプルとして使ってみました。

これを下記のようにしてコンパイルして、依存関係を出力してみました。

g++ -m64 hoge.cpp
ldd a.out

linux-vdso.so.1 =>  (0x00007fffe4bff000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f7d2f7ec000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f7d2f42c000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f7d2f12f000)
/lib64/ld-linux-x86-64.so.2 (0x00007f7d2fb05000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f7d2ef19000)

確かに、libstdc++.so に依存しているのがわかります。

スタティックリンクのオプション(-static-libstdc++) をつけて同様にやってみます。

g++ -m64 hoge.cpp -static-libstdc++
ldd a.out

linux-vdso.so.1 =>  (0x00007fffedae7000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fe5fb960000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe5fb5a0000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe5fbb8f000)

確かに、libstdc++.so への依存が消えましたが、Cランタイムへの動的リンクはそのまま残っているようです。
この libstdc++スタティックリンクのオプションは、gcc 4.5 から導入されたとのことです。

ちなみに、-static オプションをつけると全てスタティックリンクしてくれるような感じです。

g++ -m32 hoge.cpp -static
ldd a.out

動的実行ファイルではありません

注意点

dlopenを利用して共有ライブラリをロードする際に、対象の共有ライブラリがダイナミックリンクした libstc++を使う場合、その so は使えないそうです。


gccによるCランタイムのリンク方法

VisualStudioでは、コンパイル&リンクしたコード(ビルドしたコード)をC/C++のランタイムをスタティックリンクするかダイナミックリンクするかの指定が、コード生成の部分でできる。exeを持ってきてそのまま動く、ということをさせるにはスタティックリンクで作成し、配布した方が楽です。

同じことが gcc (linux)の世界ではどうなのだろうと調査してみたところ、C/C++のランタイムをスタティックにリンクする方法があるとのことでした。しばらくはこれについて調べてみようと思います。ただlinuxにおいてはglibc,stdc++の .so (共有ライブラリ)がすでにインストールされていることがとても多いので、特定のバージョンのCRTを使うということさえなければ、共有ライブラリを使うタイプでコンパイルしておいて問題はなさそうに思います。

続きを読む