[セットトップ]AndroidベースのELF PLT/GOTシンボルリダイレクトプロセスおよびELF Hook実装(byローエンドコード農2014.10.27)
引用する
この技術文を書く理由は、主に2つあります.その1つは、PLT/GOTシンボルのリダイレクトプロセスを記述するネット上のほとんどの文章がx 86向けであることを発見したことである.例えば、「Redirecting functions in shared ELF libraries」は非常によく書かれている.その過程はARMと非常に似ているが、CPUシステムが異なるため、命令実現の違いは非常に大きい. 二つ目は、ELFファイルフォーマットに関するネット上のほとんどの紹介であり、リンクビュー(Linking View)に基づいており、リンクビューはセクション(Section)に基づいてELFを解析している.しかし、ダイナミックリンクライブラリはロード中、linkerはELFのセグメント(Segment)情報にのみ注目します.そのため、ELFのセクション情報が完全に改ざんされたり削除されたりしても、linkerのロードプロセスには影響しません.これにより、IDA、readelfなどの静的解析ツールが分析することを防止することができ、一般的にシェルを追加したELFファイルにはこのような処理があります.このようなELFファイルでは、hook機能を実現するには、実行ビュー(Execution View)に基づいてシンボル解析を行う必要があります.
の準備を
下に読む前に、ELFファイルフォーマットとARMアセンブリについて大まかな理解があることを確認してください.参考ガイド: ELFファイルフォーマット分析; ARMドキュメント;
準備ツール: readelf(NDK含む) objdump(NDK含む) IDA Pro 6.4以上 Android本体またはシミュレータ シンボルリダイレクト
ARMでは、一般的なリダイレクトタイプは、主に3種類あり、それぞれR_ARM_JUMP_SLOT、R_ARM_ABS 32およびR_ARM_GLOB_DATではhook elf関数を必要としますが、この3つのリダイレクトタイプを同時に処理する必要があります.
例
サンプルコードを見て
このコードは、strlenをグローバル関数ポインタ、ローカル関数ポインタ、および直接呼び出しの3つの異なる方法で呼び出します.次に、この例について、3つの呼び出し分析を分析します.
まずreadelfを使用して、次のようにリダイレクトテーブルを表示します.
はい.rel.pltと.rel.dynの2つのsectionでは,全部で4つのstrlenが現れていることが分かった.まずそれらの重要な情報を記録し,後で分析すると非常に役に立つ.それぞれ
.rel.dyn 0000AF08 R_ARM_GLOB_DAT.rel.dyn 0000B004 R_ARM_ABS32.rel.dyn 0000B008 R_ARM_ABS32.rel.plt 0000AFDC R_ARM_JUMP_SLOT
コードではstrlenを6回呼び出しましたが、なぜ4回しか現れなかったのでしょうか.また、それらの間にどのように対応しているのか、これらの問題を持ってアセンブリコードを分析します.コンパイルされたsoをIDAにドラッグすると、サンプルコードの命令が表示されます.
まずいくつかの重要な住所を見つけて、それらはそれぞれ GLOBAL_OFFSET_TABLE: 0x0000AF34 strlen_ptr: 0x0000AF08 __imp_strlen: 0x0000B0C8 global_strlen1_ptr: 0x0000AF0C global_strlen1: 0x0000B004 global_strlen2_ptr: 0x0000AF10 global_strlen2: 0x0000B008
グローバル関数ポインタ外部関数の呼び出し
global_strlen 1とglobal_strlen 2の呼び出しは、0 x 00050 E 4と0 x 00050 F 4の両方のBLX命令に対応し、最終R 3の値を算出することによりそれぞれ*global_strlen 1と*global_strlen 2、global_strlen 1とglobal_strlen 2の値は、ちょうど対応する位置にある.rel.dynの2つのR_ARM_ABS 32の再配置項について,グローバル関数ポインタにより外部関数を呼び出し,その再配置タイプはR_であると結論した.ARM_ABS 32は、位置する.rel.dynセクション.
globalのみを分析しますstrlen 1の呼び出しプロセスは、まずglobal_にナビゲートします.strlen1_ptr(0 x 0000 AF 0 C)は、位置する.gotセクション、GLOBAL_OFFSET_TABLEの上.そしてglobal_を通ってstrlen1_ptrは0 x 0000 B 004(.dataセクションにある)に、最後に0 x 0000 B 004を介して最終的な関数アドレスに位置するので、R_ARM_ABS 32の再配置項目のOffsetは、最終的に呼び出す関数アドレスのアドレス(すなわち、関数ポインタのポインタ)を指し、再配置プロセス全体が先頭に立つ.got,また.gotは.date.次はgotセグメントの16進表現セグメント:
最後に、0 x 0000 B 0 C 8アドレスシートの命令はすべて0であることが分かった.ダイナミックリンクすると、linkerは0 x 0000 B 004アドレスの値を上書きし、strlenの真のアドレスを指す(現在の0 x 0000 B 0 C 8ではなく、少し迂回する).
ローカル関数ポインタ外部関数の呼び出し
local_strlen 1とlocal_strlen 2の呼び出しは、0 x 0005100と0 x 000510 Cの両方のBLX命令に対応し、最終R 3の値を計算することで*strlen_prt、すなわち0 x 0000 AF 08は、ちょうど対応する位置にある.rel.dynのR_ARM_GLOB_DATは項を再配置するので,局所関数ポインタ方式で外部関数を呼び出し,その再配置タイプはR_であると結論した.ARM_GLOB_DATは、位置する.re.dynセクション.
私たちはlocalだけを分析します.strlen 1の呼び出しプロセスは、まずstrlen_にナビゲートします.prt(0 x 0000 AF 08)は、位置する.gotセクション、GLOBAL_OFFSET_TABLEの上、そしてstrlen_を通りますprtは、0 x 0000 B 0 C 8に位置決めされ、上記の分析の結果と同じであるため、R_ARM_GLOB_DATのリセットOffsetは、最終的に呼び出す関数アドレスのアドレス(すなわち、関数ポインタのポインタ)を指す、以下である.gotセグメントの16進表現セグメント:
なお、0 x 00050 D 8のコマンド「STR 3,[SP,#0 x 40+var_24]」は、既に関数の実際のアドレスがスタックに保存されているため、GOTテーブルを変更してもスタックの値に影響しないため、このような再配置タイプではアドレスを変更してhookすることはできない.
外部関数を直接呼び出す
最後にstrlenの直接呼び出しを見て、0 x 000511 Aと0 x 0005122の2つのBLX命令に対応して、最後にそれらはすべて指向します.pltセクション命令は、次のようになります.
最後にPC指向*strlen_ptr_0、すなわちstrlen_ptr_0のアドレス0 x 0000 AFDCである.gotセクション、0 x 0000 AFDCアドレス値のちょうど0 x 0000 B 0 C 8は、どんなによく知っている影ですか.したがって,外部関数を直接呼び出し,その再位置決めタイプはR_であると結論した.ARM_JUMP_SLOTは、位置する.re.pltセクションは、Offsetが最終呼び出し関数アドレスのアドレス(すなわち、関数ポインタのポインタ)を指す.全体の過程は先着だ.plt、また.gotは、最後に本当の関数アドレスに位置決めされます.
このセクションの解析では、IDAとobjdumpの逆コンパイルの結果に違いがあることがわかりました.以下はobjdumpを介したアセンブリ命令です.
afdcのアドレスは0 x 0000 2 c 50を指し、0 x 0000 2 c 50はちょうどPLT[0]であり、命令は以下の通りである.
2 c 5 cでの命令実行後、最終pcは0 x 0000 af 3 cを指し、ちょうどGLOBAL_であるOFFSET_TABLE+8、すなわちGOT[2]であり、0 x 0000 af 3 cに見られる.
まとめ
IDAとobudumpの2つのツールを逆コンパイルした命令はPLTGOTの過程でいくつかの違いがありますが、Androidではlazyバインドがサポートされていないため、この違いは影響しません.同時に我々は非常に重要な結論を出した:R_ARM_ABS32、R_ARM_GLOB_DATとR_ARM_JUMP_SLOTの再配置項はコードでは異なるが,offsetは関数のポインタを指すポインタであり,これは我々が次にelfhookを行うのに非常に有用である.
実行ビュー解析ELFに基づく
「Redirecting functions in shared ELF libraries」という文章が提供した例は、リンクビューに基づいてELFを解析したもので、実行ビューに基づいて解析するよりも、後の論理は基本的に同じであり、segmentによって見つけることが肝心である.dynsym、.dynstr、.rel.pltとrel.dyn、およびそれらの項目数.
Program Header Tableで初めて見つけたタイプはPT_DYNAMICのセグメントは、その内容が実際に対応する.dynamic,この段落の内容はElf 32_に対応するDynタイプの配列です.構造体は次のようになります.
この配列を巡ることで、必要なすべての情報を見つけることができます.私はそれらの対応関係を列挙します. DT_HASH -> .hash DT_SYMTAB & DT_SYMENT -> .dynsym DT_STRTAB & DT_STRSZ -> .dynstr PLTREL(RELかRELAかを決める)&(DT_REL|DT_RELA)&(DT_RELSZ|DT_RELASZ)&(DT_RELENT|DT_RELAENT)->rel.dyn DT_JMPREL & DT_PLTRELSZ & (DT_RELENT | DT_RELAENT) -> .rel.plt FINI_ARRAY & FINI_ARRAYSZ -> .fini_array INIT_ARRAY & INIT_ARRAYSZ -> .init_array
これは検索に関連するコードです.
しかし、PTを通過できない値があります.DYNAMICセグメントが得たのは、それは.dynsymの項数、私は最後に融通の方法で得た.によって...dynstrの2つのセグメントは隣接するので、2つのアドレスを減算すれば得る.dynsymの全長はsizeof(Elf 32_Sym)を除く得ることができる.dynsymの項数、もっと良い方法があれば、私に言ってください.
ELF Hook
上の紹介があったら、ELF Hookを書くのは簡単です.キーコードを貼ります.
最後にテストのコードです.
印刷結果から、local_strlen 1とlocal_strlen 2は前述したように影響を受けていないが,関数が再び呼び出されると有効になり,原因は解析されない.テストの結果はもう出しません.試してみましょう.
GitHupアドレス
完全なコード、を参照https://github.com/boyliang/AllHookInOne.git
この技術文を書く理由は、主に2つあります.
の準備を
下に読む前に、ELFファイルフォーマットとARMアセンブリについて大まかな理解があることを確認してください.参考ガイド:
準備ツール:
ARMでは、一般的なリダイレクトタイプは、主に3種類あり、それぞれR_ARM_JUMP_SLOT、R_ARM_ABS 32およびR_ARM_GLOB_DATではhook elf関数を必要としますが、この3つのリダイレクトタイプを同時に処理する必要があります.
例
サンプルコードを見て
typedef int (*strlen_fun)(const char *);
strlen_fun global_strlen1 = (strlen_fun)strlen;
strlen_fun global_strlen2 = (strlen_fun)strlen;
#define SHOW(x) LOGI("%s is %d", #x, x)
extern "C" jint Java_com_example_allhookinone_HookUtils_elfhook(JNIEnv *env, jobject thiz){
const char *str = "helloworld";
strlen_fun local_strlen1 = (strlen_fun)strlen;
strlen_fun local_strlen2 = (strlen_fun)strlen;
int len0 = global_strlen1(str);
int len1 = global_strlen2(str);
int len2 = local_strlen1(str);
int len3 = local_strlen2(str);
int len4 = strlen(str);
int len5 = strlen(str);
SHOW(len0);
SHOW(len1);
SHOW(len2);
SHOW(len3);
SHOW(len4);
SHOW(len5);
return 0;
}
このコードは、strlenをグローバル関数ポインタ、ローカル関数ポインタ、および直接呼び出しの3つの異なる方法で呼び出します.次に、この例について、3つの呼び出し分析を分析します.
まずreadelfを使用して、次のようにリダイレクトテーブルを表示します.
Relocation section '.rel.dyn' at offset 0x2a48 contains 17 entries:
Offset Info Type Sym.Value Sym. Name
0000ade0 00000017 R_ARM_RELATIVE
0000af00 00000017 R_ARM_RELATIVE
0000af0c 00000017 R_ARM_RELATIVE
0000af10 00000017 R_ARM_RELATIVE
0000af18 00000017 R_ARM_RELATIVE
0000af1c 00000017 R_ARM_RELATIVE
0000af20 00000017 R_ARM_RELATIVE
0000af24 00000017 R_ARM_RELATIVE
0000af28 00000017 R_ARM_RELATIVE
0000af30 00000017 R_ARM_RELATIVE
0000aefc 00003215 R_ARM_GLOB_DAT 00000000 __stack_chk_guard
0000af04 00003715 R_ARM_GLOB_DAT 00000000 __page_size
0000af08 00004e15 R_ARM_GLOB_DAT 00000000 strlen
0000b004 00004e02 R_ARM_ABS32 00000000 strlen
0000b008 00004e02 R_ARM_ABS32 00000000 strlen
0000af14 00006615 R_ARM_GLOB_DAT 00000000 __gnu_Unwind_Find_exid
0000af2c 00007415 R_ARM_GLOB_DAT 00000000 __cxa_call_unexpected
...
...
Relocation section '.rel.plt' at offset 0x2ad0 contains 48 entries:
Offset Info Type Sym.Value Sym. Name
0000af40 00000216 R_ARM_JUMP_SLOT 00000000 __cxa_atexit
0000af44 00000116 R_ARM_JUMP_SLOT 00000000 __cxa_finalize
0000af48 00001716 R_ARM_JUMP_SLOT 00000000 memcpy
...
0000afd4 00004c16 R_ARM_JUMP_SLOT 00000000 fgets
0000afd8 00004d16 R_ARM_JUMP_SLOT 00000000 fclose
0000afdc 00004e16 R_ARM_JUMP_SLOT 00000000 strlen
0000afe0 00004f16 R_ARM_JUMP_SLOT 00000000 strncmp
...
...
はい.rel.pltと.rel.dynの2つのsectionでは,全部で4つのstrlenが現れていることが分かった.まずそれらの重要な情報を記録し,後で分析すると非常に役に立つ.それぞれ
.rel.dyn 0000AF08 R_ARM_GLOB_DAT.rel.dyn 0000B004 R_ARM_ABS32.rel.dyn 0000B008 R_ARM_ABS32.rel.plt 0000AFDC R_ARM_JUMP_SLOT
コードではstrlenを6回呼び出しましたが、なぜ4回しか現れなかったのでしょうか.また、それらの間にどのように対応しているのか、これらの問題を持ってアセンブリコードを分析します.コンパイルされたsoをIDAにドラッグすると、サンプルコードの命令が表示されます.
.text:000050BC EXPORT Java_com_example_allhookinone_HookUtils_elfhook
.text:000050BC Java_com_example_allhookinone_HookUtils_elfhook
.text:000050BC
.text:000050BC var_40 = -0x40
.text:000050BC var_38 = -0x38
.text:000050BC var_34 = -0x34
.text:000050BC s = -0x2C
.text:000050BC var_28 = -0x28
.text:000050BC var_24 = -0x24
.text:000050BC var_20 = -0x20
.text:000050BC var_1C = -0x1C
.text:000050BC var_18 = -0x18
.text:000050BC var_14 = -0x14
.text:000050BC var_10 = -0x10
.text:000050BC var_C = -0xC
.text:000050BC
.text:000050BC PUSH {R4,LR}
.text:000050BE SUB SP, SP, #0x38
.text:000050C0 STR R0, [SP,#0x40+var_34]
.text:000050C2 STR R1, [SP,#0x40+var_38]
.text:000050C4 LDR R4, =(_GLOBAL_OFFSET_TABLE_ - 0x50CA)
.text:000050C6 ADD R4, PC ; _GLOBAL_OFFSET_TABLE_
.text:000050C8 LDR R3, =(aHelloworld - 0x50CE)
.text:000050CA ADD R3, PC ; "helloworld"
.text:000050CC STR R3, [SP,#0x40+s]
.text:000050CE LDR R3, =(strlen_ptr - 0xAF34)
.text:000050D0 LDR R3, [R4,R3] ; __imp_strlen
.text:000050D2 STR R3, [SP,#0x40+var_28]
.text:000050D4 LDR R3, =(strlen_ptr - 0xAF34)
.text:000050D6 LDR R3, [R4,R3] ; __imp_strlen
.text:000050D8 STR R3, [SP,#0x40+var_24]
.text:000050DA LDR R3, =(global_strlen1_ptr - 0xAF34)
.text:000050DC LDR R3, [R4,R3] ; global_strlen1
.text:000050DE LDR R3, [R3]
.text:000050E0 LDR R2, [SP,#0x40+s]
.text:000050E2 MOVS R0, R2
.text:000050E4 BLX R3
.text:000050E6 MOVS R3, R0
.text:000050E8 STR R3, [SP,#0x40+var_20]
.text:000050EA LDR R3, =(global_strlen2_ptr - 0xAF34)
.text:000050EC LDR R3, [R4,R3] ; global_strlen2
.text:000050EE LDR R3, [R3]
.text:000050F0 LDR R2, [SP,#0x40+s]
.text:000050F2 MOVS R0, R2
.text:000050F4 BLX R3
.text:000050F6 MOVS R3, R0
.text:000050F8 STR R3, [SP,#0x40+var_1C]
.text:000050FA LDR R2, [SP,#0x40+s]
.text:000050FC LDR R3, [SP,#0x40+var_28]
.text:000050FE MOVS R0, R2
.text:00005100 BLX R3
.text:00005102 MOVS R3, R0
.text:00005104 STR R3, [SP,#0x40+var_18]
.text:00005106 LDR R2, [SP,#0x40+s]
.text:00005108 LDR R3, [SP,#0x40+var_24]
.text:0000510A MOVS R0, R2
.text:0000510C BLX R3
.text:0000510E MOVS R3, R0
.text:00005110 STR R3, [SP,#0x40+var_14]
.text:00005112 LDR R3, [SP,#0x40+s]
.text:00005114 MOVS R0, R3 ; s
.text:00005116 BLX strlen
.text:0000511A MOVS R3, R0
.text:0000511C STR R3, [SP,#0x40+var_10]
.text:0000511E LDR R3, [SP,#0x40+s]
.text:00005120 MOVS R0, R3 ; s
.text:00005122 BLX strlen
.text:00005126 MOVS R3, R0
...
...
.text:000051CA ADD SP, SP, #0x38
.text:000051CC POP {R4,PC}
.text:000051CC ; End of function Java_com_example_allhookinone_HookUtils_elfhook
まずいくつかの重要な住所を見つけて、それらはそれぞれ
グローバル関数ポインタ外部関数の呼び出し
global_strlen 1とglobal_strlen 2の呼び出しは、0 x 00050 E 4と0 x 00050 F 4の両方のBLX命令に対応し、最終R 3の値を算出することによりそれぞれ*global_strlen 1と*global_strlen 2、global_strlen 1とglobal_strlen 2の値は、ちょうど対応する位置にある.rel.dynの2つのR_ARM_ABS 32の再配置項について,グローバル関数ポインタにより外部関数を呼び出し,その再配置タイプはR_であると結論した.ARM_ABS 32は、位置する.rel.dynセクション.
globalのみを分析しますstrlen 1の呼び出しプロセスは、まずglobal_にナビゲートします.strlen1_ptr(0 x 0000 AF 0 C)は、位置する.gotセクション、GLOBAL_OFFSET_TABLEの上.そしてglobal_を通ってstrlen1_ptrは0 x 0000 B 004(.dataセクションにある)に、最後に0 x 0000 B 004を介して最終的な関数アドレスに位置するので、R_ARM_ABS 32の再配置項目のOffsetは、最終的に呼び出す関数アドレスのアドレス(すなわち、関数ポインタのポインタ)を指し、再配置プロセス全体が先頭に立つ.got,また.gotは.date.次はgotセグメントの16進表現セグメント:
...
0000AF0C 04 B0 00 00 08 B0 00 00 DC B0 00 00 B4 87 00 00
0000AF1C F4 84 00 00 60 5B 00 00 58 5B 00 00 50 5B 00 00
0000AF2C EC B0 00 00 FC 8C 00 00 00 00 00 00 00 00 00 00
...
0000B004 C8 B0 00 00 C8 B0 00 00 ?? ?? ?? ?? ?? ?? ?? ??
0000B014 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??
0000B024 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
...
0000B0C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000B0D8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
...
最後に、0 x 0000 B 0 C 8アドレスシートの命令はすべて0であることが分かった.ダイナミックリンクすると、linkerは0 x 0000 B 004アドレスの値を上書きし、strlenの真のアドレスを指す(現在の0 x 0000 B 0 C 8ではなく、少し迂回する).
ローカル関数ポインタ外部関数の呼び出し
local_strlen 1とlocal_strlen 2の呼び出しは、0 x 0005100と0 x 000510 Cの両方のBLX命令に対応し、最終R 3の値を計算することで*strlen_prt、すなわち0 x 0000 AF 08は、ちょうど対応する位置にある.rel.dynのR_ARM_GLOB_DATは項を再配置するので,局所関数ポインタ方式で外部関数を呼び出し,その再配置タイプはR_であると結論した.ARM_GLOB_DATは、位置する.re.dynセクション.
私たちはlocalだけを分析します.strlen 1の呼び出しプロセスは、まずstrlen_にナビゲートします.prt(0 x 0000 AF 08)は、位置する.gotセクション、GLOBAL_OFFSET_TABLEの上、そしてstrlen_を通りますprtは、0 x 0000 B 0 C 8に位置決めされ、上記の分析の結果と同じであるため、R_ARM_GLOB_DATのリセットOffsetは、最終的に呼び出す関数アドレスのアドレス(すなわち、関数ポインタのポインタ)を指す、以下である.gotセグメントの16進表現セグメント:
0000AF08 C8 B0 00 00 04 B0 00 00 08 B0 00 00 DC B0 00 00
0000AF18 B4 87 00 00 F4 84 00 00 60 5B 00 00 58 5B 00 00
0000AF28 50 5B 00 00 EC B0 00 00 FC 8C 00 00 00 00 00 00
...
0000B0C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000B0D8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
...
なお、0 x 00050 D 8のコマンド「STR 3,[SP,#0 x 40+var_24]」は、既に関数の実際のアドレスがスタックに保存されているため、GOTテーブルを変更してもスタックの値に影響しないため、このような再配置タイプではアドレスを変更してhookすることはできない.
外部関数を直接呼び出す
最後にstrlenの直接呼び出しを見て、0 x 000511 Aと0 x 0005122の2つのBLX命令に対応して、最後にそれらはすべて指向します.pltセクション命令は、次のようになります.
.plt:00002E38 ADR R12, 0x2E40
.plt:00002E3C ADD R12, R12, #0x8000
.plt:00002E40 LDR PC, [R12,#(strlen_ptr_0 - 0xAE40)]! ; __imp_strlen
...
0000AFDC C8 B0 00 00 CC B0 00 00 D0 B0 00 00 D4 B0 00 00
0000AFEC D8 B0 00 00 DC B0 00 00 E0 B0 00 00 E4 B0 00 00
0000AFFC E8 B0 00 00 00 00 00 00 C8 B0 00 00 C8 B0 00 00
...
最後にPC指向*strlen_ptr_0、すなわちstrlen_ptr_0のアドレス0 x 0000 AFDCである.gotセクション、0 x 0000 AFDCアドレス値のちょうど0 x 0000 B 0 C 8は、どんなによく知っている影ですか.したがって,外部関数を直接呼び出し,その再位置決めタイプはR_であると結論した.ARM_JUMP_SLOTは、位置する.re.pltセクションは、Offsetが最終呼び出し関数アドレスのアドレス(すなわち、関数ポインタのポインタ)を指す.全体の過程は先着だ.plt、また.gotは、最後に本当の関数アドレスに位置決めされます.
このセクションの解析では、IDAとobjdumpの逆コンパイルの結果に違いがあることがわかりました.以下はobjdumpを介したアセンブリ命令です.
00002e38 <strlen@plt>:
2e38: e28fc600 add ip, pc, #0, 12
2e3c: e28cca08 add ip, ip, #8, 20 ; 0x8000
2e40: e5bcf19c ldr pc, [ip, #412]! ; 0x19c
...
...
afd8: 00002c50 andeq r2, r0, r0, asr ip
afdc: 00002c50 andeq r2, r0, r0, asr ip
afe0: 00002c50 andeq r2, r0, r0, asr ip
afe4: 00002c50 andeq r2, r0, r0, asr ip
afdcのアドレスは0 x 0000 2 c 50を指し、0 x 0000 2 c 50はちょうどPLT[0]であり、命令は以下の通りである.
00002c50 <__cxa_atexit@plt-0x14>:
2c50: e52de004 push {lr} ; (str lr, [sp, #-4]!)
2c54: e59fe004 ldr lr, [pc, #4] ; 2c60 <__cxa_atexit@plt-0x4>
2c58: e08fe00e add lr, pc, lr
2c5c: e5bef008 ldr pc, [lr, #8]!
2c60: 000082d4 ldrdeq r8, [r0], -r4
2 c 5 cでの命令実行後、最終pcは0 x 0000 af 3 cを指し、ちょうどGLOBAL_であるOFFSET_TABLE+8、すなわちGOT[2]であり、0 x 0000 af 3 cに見られる.
0000AF3C 00 00 00 00 28 B0 00 00 24 B0 00 00 2C B0 00 00
0000AF4C 30 B0 00 00 34 B0 00 00 38 B0 00 00 3C B0 00 00
GOT[2] 0, android lazy , so ,linker GOT[n](n>=2) , GOT[2] , Android , PLT/GOT 。 。
まとめ
IDAとobudumpの2つのツールを逆コンパイルした命令はPLTGOTの過程でいくつかの違いがありますが、Androidではlazyバインドがサポートされていないため、この違いは影響しません.同時に我々は非常に重要な結論を出した:R_ARM_ABS32、R_ARM_GLOB_DATとR_ARM_JUMP_SLOTの再配置項はコードでは異なるが,offsetは関数のポインタを指すポインタであり,これは我々が次にelfhookを行うのに非常に有用である.
実行ビュー解析ELFに基づく
「Redirecting functions in shared ELF libraries」という文章が提供した例は、リンクビューに基づいてELFを解析したもので、実行ビューに基づいて解析するよりも、後の論理は基本的に同じであり、segmentによって見つけることが肝心である.dynsym、.dynstr、.rel.pltとrel.dyn、およびそれらの項目数.
Program Header Tableで初めて見つけたタイプはPT_DYNAMICのセグメントは、その内容が実際に対応する.dynamic,この段落の内容はElf 32_に対応するDynタイプの配列です.構造体は次のようになります.
/* Dynamic structure */
typedef struct {
Elf32_Sword d_tag; /* controls meaning of d_val */
union {
Elf32_Word d_val; /* Multiple meanings - see d_tag */
Elf32_Addr d_ptr; /* program virtual address */
} d_un;
} Elf32_Dyn;
この配列を巡ることで、必要なすべての情報を見つけることができます.私はそれらの対応関係を列挙します.
これは検索に関連するコードです.
void getElfInfoBySegmentView(ElfInfo &info, const ElfHandle *handle){
info.handle = handle;
info.elf_base = (uint8_t *) handle->base;
info.ehdr = reinterpret_cast<Elf32_Ehdr *>(info.elf_base);
// may be wrong
info.shdr = reinterpret_cast<Elf32_Shdr *>(info.elf_base + info.ehdr->e_shoff);
info.phdr = reinterpret_cast<Elf32_Phdr *>(info.elf_base + info.ehdr->e_phoff);
info.shstr = NULL;
Elf32_Phdr *dynamic = NULL;
Elf32_Word size = 0;
getSegmentInfo(info, PT_DYNAMIC, &dynamic, &size, &info.dyn);
if(!dynamic){
LOGE("[-] could't find PT_DYNAMIC segment");
exit(-1);
}
info.dynsz = size / sizeof(Elf32_Dyn);
Elf32_Dyn *dyn = info.dyn;
for(int i=0; i<info.dynsz; i++, dyn++){
switch(dyn->d_tag){
case DT_SYMTAB:
info.sym = reinterpret_cast<Elf32_Sym *>(info.elf_base + dyn->d_un.d_ptr);
break;
case DT_STRTAB:
info.symstr = reinterpret_cast<const char *>(info.elf_base + dyn->d_un.d_ptr);
break;
case DT_REL:
info.reldyn = reinterpret_cast<Elf32_Rel *>(info.elf_base + dyn->d_un.d_ptr);
break;
case DT_RELSZ:
info.reldynsz = dyn->d_un.d_val / sizeof(Elf32_Rel);
break;
case DT_JMPREL:
info.relplt = reinterpret_cast<Elf32_Rel *>(info.elf_base + dyn->d_un.d_ptr);
break;
case DT_PLTRELSZ:
info.relpltsz = dyn->d_un.d_val / sizeof(Elf32_Rel);
break;
case DT_HASH:
uint32_t *rawdata = reinterpret_cast<uint32_t *>(info.elf_base + dyn->d_un.d_ptr);
info.nbucket = rawdata[0];
info.nchain = rawdata[1];
info.bucket = rawdata + 2;
info.chain = info.bucket + info.nbucket;
break;
}
}
//because .dynsym is next to .dynstr, so we can caculate the symsz simply
info.symsz = ((uint32_t)info.symstr - (uint32_t)info.sym)/sizeof(Elf32_Sym);
}
しかし、PTを通過できない値があります.DYNAMICセグメントが得たのは、それは.dynsymの項数、私は最後に融通の方法で得た.によって...dynstrの2つのセグメントは隣接するので、2つのアドレスを減算すれば得る.dynsymの全長はsizeof(Elf 32_Sym)を除く得ることができる.dynsymの項数、もっと良い方法があれば、私に言ってください.
ELF Hook
上の紹介があったら、ELF Hookを書くのは簡単です.キーコードを貼ります.
#define R_ARM_ABS32 0x02
#define R_ARM_GLOB_DAT 0x15
#define R_ARM_JUMP_SLOT 0x16
int elfHook(const char *soname, const char *symbol, void *replace_func, void **old_func){
assert(old_func);
assert(replace_func);
assert(symbol);
ElfHandle* handle = openElfBySoname(soname);
ElfInfo info;
getElfInfoBySegmentView(info, handle);
Elf32_Sym *sym = NULL;
int symidx = 0;
findSymByName(info, symbol, &sym, &symidx);
if(!sym){
LOGE("[-] Could not find symbol %s", symbol);
goto fails;
}else{
LOGI("[+] sym %p, symidx %d.", sym, symidx);
}
for (int i = 0; i < info.relpltsz; i++) {
Elf32_Rel& rel = info.relplt[i];
if (ELF32_R_SYM(rel.r_info) == symidx && ELF32_R_TYPE(rel.r_info) == R_ARM_JUMP_SLOT) {
void *addr = (void *) (info.elf_base + rel.r_offset);
if (replaceFunc(addr, replace_func, old_func))
goto fails;
//only once
break;
}
}
for (int i = 0; i < info.reldynsz; i++) {
Elf32_Rel& rel = info.reldyn[i];
if (ELF32_R_SYM(rel.r_info) == symidx &&
(ELF32_R_TYPE(rel.r_info) == R_ARM_ABS32
|| ELF32_R_TYPE(rel.r_info) == R_ARM_GLOB_DAT)) {
void *addr = (void *) (info.elf_base + rel.r_offset);
if (replaceFunc(addr, replace_func, old_func))
goto fails;
}
}
fails:
closeElfBySoname(handle);
return 0;
}
最後にテストのコードです.
typedef int (*strlen_fun)(const char *);
strlen_fun old_strlen = NULL;
size_t my_strlen(const char *str){
LOGI("strlen was called.");
int len = old_strlen(str);
return len * 2;
}
strlen_fun global_strlen1 = (strlen_fun)strlen;
strlen_fun global_strlen2 = (strlen_fun)strlen;
#define SHOW(x) LOGI("%s is %d", #x, x)
extern "C" jint Java_com_example_allhookinone_HookUtils_elfhook(JNIEnv *env, jobject thiz){
const char *str = "helloworld";
strlen_fun local_strlen1 = (strlen_fun)strlen;
strlen_fun local_strlen2 = (strlen_fun)strlen;
int len0 = global_strlen1(str);
int len1 = global_strlen2(str);
int len2 = local_strlen1(str);
int len3 = local_strlen2(str);
int len4 = strlen(str);
int len5 = strlen(str);
LOGI("hook before:");
SHOW(len0);
SHOW(len1);
SHOW(len2);
SHOW(len3);
SHOW(len4);
SHOW(len5);
elfHook("libonehook.so", "strlen", (void *)my_strlen, (void **)&old_strlen);
len0 = global_strlen1(str);
len1 = global_strlen2(str);
len2 = local_strlen1(str);
len3 = local_strlen2(str);
len4 = strlen(str);
len5 = strlen(str);
LOGI("hook after:");
SHOW(len0);
SHOW(len1);
SHOW(len2);
SHOW(len3);
SHOW(len4);
SHOW(len5);
return 0;
}
印刷結果から、local_strlen 1とlocal_strlen 2は前述したように影響を受けていないが,関数が再び呼び出されると有効になり,原因は解析されない.テストの結果はもう出しません.試してみましょう.
GitHupアドレス
完全なコード、を参照https://github.com/boyliang/AllHookInOne.git