Android Hookフレームワークadbiソースコードの浅い分析(二)
2928 ワード
二、libbase
実際にSOライブラリをロードした後、hookの機能は完全に自分でダイナミックライブラリで実現することができます.adbiの著者らは,我々の使用を容易にするために,一般的なhookフレームワークツールであるlibbaseライブラリを作成した.libbaseは依然として2つの問題を解決している:1.hookするターゲット関数アドレスを取得します.2.関数にバイナリパッチを適用するinline hook.
hook関数アドレスの取得方法についてはここでは後述しない.直接inline hook部分を見て、この部分の機能はbasehookです.cのhook()関数で実装するには,まずhook_を見る.t構造体:
hook_tは標準inline hook構造体であり、ジャンプ命令/ジャンプアドレス/命令セット/hook関数名などの情報を保存している.ARMはARMとThumbの2種類の命令セットを使用しているので、コードで区別する必要があります.
このように判断する根拠は,コンパイラがThumb命令セットを用いて1つの関数をコンパイルする際に,真のマッピングアドレスの最後の位置’1’をシンボルアドレスに自動的に付与することで,シームレスなThumb命令セット関数とArm命令セットコードの混成を実現できることである.次に、ARM命令セットブランチの処理フローを見てみましょう.これは、この問題解決の核心部分です.
まずhook_を埋めますt構造体は、最初のforサイクルで元のアドレスにある3つの命令の合計12バイトを保存する.2番目のforループは新しいジャンプ命令で上書きされ、キーの3つの命令はjump[0]-[2]にそれぞれ保存されます.
jump[0]は0 xe 59 ff 000を付与し、ARMアセンブリldr pc,[pc,#0]に翻訳する.pcレジスタが読み出した値は現在の命令アドレスに8を加算するため、この命令は実際にjump[2]の値をpcレジスタにロードする.jump[2]はhook関数アドレスを保存します.jump[1]は4バイトの占有にのみ使用されます.Thumbブランチの原理はARMブランチと一致し,解析を省略した.
次に、関数が最後にhook_を呼び出したことに気づきました.Cacheflush()関数:
現代のプロセッサには、実行効率を向上させるための命令キャッシュがあることが知られています.前に変更したのはメモリの中の命令で、キャッシュの存在を防ぐために、私たちが変更した命令が実行できないように、キャッシュのリフレッシュを行う必要があります.
参考資料
[1].adbiソースhttps://github.com/crmulliner/adbi[2].minghuasweblog,ARM Cache Flush on mmap’d Buffers with __clear_cache(),March 29, 2013
実際にSOライブラリをロードした後、hookの機能は完全に自分でダイナミックライブラリで実現することができます.adbiの著者らは,我々の使用を容易にするために,一般的なhookフレームワークツールであるlibbaseライブラリを作成した.libbaseは依然として2つの問題を解決している:1.hookするターゲット関数アドレスを取得します.2.関数にバイナリパッチを適用するinline hook.
hook関数アドレスの取得方法についてはここでは後述しない.直接inline hook部分を見て、この部分の機能はbasehookです.cのhook()関数で実装するには,まずhook_を見る.t構造体:
struct hook_t {
unsigned int jump[3]; // (ARM)
unsigned int store[3]; // (ARM)
unsigned char jumpt[20]; // (Thumb)
unsigned char storet[20]; // (Thumb)
unsigned int orig; // hook
unsigned int patch; //
unsigned char thumb; // ,1 Thumb,2 ARM
unsigned char name[128]; // hook
void *data;
};
hook_tは標準inline hook構造体であり、ジャンプ命令/ジャンプアドレス/命令セット/hook関数名などの情報を保存している.ARMはARMとThumbの2種類の命令セットを使用しているので、コードで区別する必要があります.
if (addr % 4 == 0) {
/* ARM */
} else {
/* Thumb */
}
このように判断する根拠は,コンパイラがThumb命令セットを用いて1つの関数をコンパイルする際に,真のマッピングアドレスの最後の位置’1’をシンボルアドレスに自動的に付与することで,シームレスなThumb命令セット関数とArm命令セットコードの混成を実現できることである.次に、ARM命令セットブランチの処理フローを見てみましょう.これは、この問題解決の核心部分です.
if (addr % 4 == 0) {
log("ARM using 0x%lx
", (unsigned long)hook_arm)
h->thumb = 0;
h->patch = (unsigned int)hook_arm;
h->orig = addr;
h->jump[0] = 0xe59ff000; // LDR pc, [pc, #0]
h->jump[1] = h->patch;
h->jump[2] = h->patch;
for (i = 0; i < 3; i++)
h->store[i] = ((int*)h->orig)[i];
for (i = 0; i < 3; i++)
((int*)h->orig)[i] = h->jump[i];
}
まずhook_を埋めますt構造体は、最初のforサイクルで元のアドレスにある3つの命令の合計12バイトを保存する.2番目のforループは新しいジャンプ命令で上書きされ、キーの3つの命令はjump[0]-[2]にそれぞれ保存されます.
jump[0]は0 xe 59 ff 000を付与し、ARMアセンブリldr pc,[pc,#0]に翻訳する.pcレジスタが読み出した値は現在の命令アドレスに8を加算するため、この命令は実際にjump[2]の値をpcレジスタにロードする.jump[2]はhook関数アドレスを保存します.jump[1]は4バイトの占有にのみ使用されます.Thumbブランチの原理はARMブランチと一致し,解析を省略した.
次に、関数が最後にhook_を呼び出したことに気づきました.Cacheflush()関数:
hook_cacheflush((unsigned int)h->orig, (unsigned int)h->orig+sizeof(h->jumpt));
現代のプロセッサには、実行効率を向上させるための命令キャッシュがあることが知られています.前に変更したのはメモリの中の命令で、キャッシュの存在を防ぐために、私たちが変更した命令が実行できないように、キャッシュのリフレッシュを行う必要があります.
void inline hook_cacheflush(unsigned int begin, unsigned int end)
{
const int syscall = 0xf0002;
__asm __volatile (
"mov r0, %0
"
"mov r1, %1
"
"mov r7, %2
"
"mov r2, #0x0
"
"svc 0x00000000
"
:
: "r" (begin), "r" (end), "r" (syscall)
: "r0", "r1", "r7"
);
}
参考資料
[1].adbiソースhttps://github.com/crmulliner/adbi[2].minghuasweblog,ARM Cache Flush on mmap’d Buffers with __clear_cache(),March 29, 2013