Post on 17-Feb-2017
Android Dynamic Framework : Native Hook Mechanism in Bionic Linker
Mai-Hsuan ChiaShih-Wei Liao
Department of Computer Science and Information EngineeringNational Taiwan University
Outline● Background● Motivation● Native Hook Mechanism● Experiment● Applications● Future works● Conclusion
Background● JNI● Android Dynamic Framework● Bionic
JNI● Enable Java code can call or can be called by native applications
JNI
Java method
JNI
Native functionC/C++
Java
Java calls nativeclass HelloWorld { private native void print(); // print() is native function public static void main(String[] args) { new HelloWorld().print(); } static { System.loadLibrary("hello"); // This loads libhello.so }}
● A framework which is able to dynamically replace Java methods in ART Runtime without modifying APKs.
Android Dynamic Framework
Android Dynamic Framework
Class A Class B
HookTable
...class linker
Method A1
Method A2
Method B1
Method B2
Android Dynamic Framework
Class A Class B
HookTable
...class linker
Method A1
Method A2
Method B1
Method B2
0. Do linking
Android Dynamic Framework
Class A Class B
HookTable
...class linker
Method A1
Method A2
Method B1
Method B2
1. Query HookTable
Android Dynamic Framework
Class A Class B
HookTable
...class linker
Method A1
Method A2
Method B1
Method B2
Replace ClassA::A1 with ClassB::B1
1. Query HookTable
Android Dynamic Framework
Class A Class B
HookTable
...class linker
Method A2
Method B1
Method B2
Method B1
2. Do method hooking
● C library in Android● Forked from BSDs rather than from GNU/Linux
○ To avoid license problems● Smaller● Faster
Bionic
● Components○ libc○ libm○ libdl (written from scratch)○ dynamic linker
■ /system/bin/linker (written from scratch)
Bionic
Motivation● Only Java methods can be replaced in Android Dynamic
Framework
Class A
Method A2
Method B1
Class B
Method B1
Method B2
JNI
libd.so
Func D1
Func D2
libe.so
Func E1
Func E2
(1) method hook
Method A3
libc.so
Func C1
Func C2
native call
hooking path
Class A
Method A2
Method B1
Class B
Method B1
Method B2
JNI
libd.so
Func D1
Func D2
libe.so
Func E1
Func E2
(1) method hook
Method A3
libc.so
Func C1
Func C2
native call
hooking path
Class A
Method A2
Method B1
Class B
Method B1
Method B2
JNI
libd.so
Func D1
Func D2
libe.so
Func E1
Func E2
(1) method hook
Method A3
libc.so
Func D1
Func C2
native call
hooking path
(2) dlopen native hook
(1) method hook
Class A
Method A2
Method B1
Class B
Method B1
Method B2
JNI
libd.so
Func D1
Func D2
libe.so
Func E1
Func E2
(1) method hook
Method A3
libc.so
Func D1
Func C2
native call
hooking path
(2) dlopen native hook
(1) method hook
(2) dlopen native hook
Class A
Method A2
Method B1
Class B
Method B1
Method B2
JNI
libd.so
Func D1
Func E2
libe.so
Func E1
Func E2
(1) method hook
Method A3
libc.so
Func D1
Func C2
native call
hooking path
(2) dlopen native hook
(1) method hook
(2) dlopen native hook
(3) native to native hook
Motivation● (1) method hook can be done in the existing Android Dynamic
Framework● However, (2) dlopen native hook and (3) native to native hook
cannot not be done.
Motivation● Native hook mechanism can do both (2) dlopen native hook and
(3) native to native hook
MotivationWith Native hook mechanism integrated,
Android Dynamic Framework can be more complete and powerful
Native hook mechanism● Implemented in Bionic Linker
Review● How Bionic Linker loads an executable● Dynamic linking flow● Dynamic loading flow
How Bionic Linker loads an executable
OS creates a process image● Based on the interpreter’s segments. high
low
Memory space
/system/bin/linker
linker
Linker links itself● __linker_init() high
low
Memory space
/system/bin/linker
Load the executable● __linker_init_post_relocation()
/system/bin/linker
high
low
Memory space
exe
executable
Get needed libraries names ● __linker_init_post_relocation()
/system/bin/linker
high
low
Memory space
executable
exe
.dynamic
DT_NEEDEDptr_to_liba1.so_nameDT_NEEDEDptr_to_liba2.so_name…DT_NULL
Get needed libraries names ● __linker_init_post_relocation()
/system/bin/linker
high
low
Memory space
executable
exe
DT_NEEDEDptr_to_liba1.so_nameDT_NEEDEDptr_to_liba2.so_name…DT_NULL
.dynamic
char needed_libraries_names[] = {“liba1.so”, “liba2.so”
}
Load needed libraries● find_libaries(exe, needed_libraries_names)
○ step 1 : load libraries and build dependencies tree
Load needed libraries● find_libaries(exe, needed_libraries_names)
○ step 1 : load libraries and build dependencies tree
exe
liba1.so
liba2.so Loaded NotLoaded
p.s.
Load needed libraries● find_libaries(exe, needed_libraries_names)
○ step 1 : load libraries and build dependencies tree
exe
liba1.so
liba2.so Loaded NotLoaded
p.s.
Load needed libraries● find_libaries(exe, needed_libraries_names)
○ step 1 : load libraries and build dependencies tree
exe
liba1.so
liba2.so
libb1.so
libb2.so
libb3.solibb4.so
...
...
Loaded NotLoaded
p.s.
Load needed libraries● find_libaries(exe, needed_libraries_names)
○ step 1 : load libraries and build dependencies tree
exe
liba1.so
liba2.so
libb2.so
libb3.solibb4.so
...
...
Loaded NotLoaded
p.s.
libb1.so
Load needed libraries● find_libaries(exe, needed_libraries_names)
○ step 1 : load libraries and build dependencies tree
exe
liba1.so
liba2.so
libb2.so
libb3.solibb4.so
...
...
...
...
...
...
... Loaded NotLoaded
p.s.
libb1.so
Load needed libraries
liba1.soexe liba2.so libb1.so libb2.so ...
● find_libaries(exe, needed_libraries_names)○ step 2 : turn dependencies tree into libraries_list in
Breadth First Search(BFS) order
libraries_listdependencies tree
Link the application and all libraries● find_libaries(exe, needed_libraries_names)
○ step 3 : relocate all to-be-relocated symbols in the application and libraries
foreach lib in libraries_list { foreach rel in lib.dynamic_relocation_table { symbol = rel.sym; soinfo_do_lookup(symbol, lib, libraries_list); }}
Link the application and all libraries● find_libaries(exe, needed_libraries_names)
○ step 3 : relocate all to-be-relocated symbols in the application and libraries
liba1.soexe liba2.so libb1.so libb2.so ...
libraries_list
exe
sym_1
sym_n
...
if sym_1 is defined in lib: sym_1 = lib.find(sym)else: lib = lib->next;
Link the application and all libraries● find_libaries(exe, needed_libraries_names)
○ step 3 : relocate all to-be-relocated symbols in the application and libraries
liba1.soexe liba2.so libb1.so libb2.so ...
libraries_list
exe
sym_1
sym_n
...
if sym_1 is defined in lib: sym_1 = lib.find(sym)else: lib = lib->next;
NOT FOUND
Link the application and all libraries● find_libaries(exe, needed_libraries_names)
○ step 3 : relocate all to-be-relocated symbols in the application and libraries
liba1.soexe liba2.so libb1.so libb2.so ...
libraries_list
exe
sym_1
sym_n
...
if sym_1 is defined in lib: sym_1 = lib.find(sym)else: lib = lib->next;
NOT FOUND
Link the application and all libraries● find_libaries(exe, needed_libraries_names)
○ step 3 : relocate all to-be-relocated symbols in the application and libraries
liba1.soexe liba2.so libb1.so libb2.so ...
libraries_list
exe
sym_1
sym_n
...
if sym_1 is defined in lib: sym_1 = lib.find(sym)else: lib = lib->next;
FOUND
ok
Link the application and all libraries● find_libaries(exe, needed_libraries_names)
○ step 3 : relocate all to-be-relocated symbols in the application and libraries
liba1.soexe liba2.so libb1.so libb2.so ...
libraries_list
exe
sym_1
sym_n
...
if sym_k is defined in lib: sym_k = lib.find(sym)else: lib = lib->next;
ok
Link the application and all libraries● find_libaries(exe, needed_libraries_names)
○ step 3 : relocate all to-be-relocated symbols in the application and libraries
liba1.soexe liba2.so libb1.so libb2.so ...
libraries_list
exe
sym_1
sym_n
...
ok
ok
Link the application and all libraries● find_libaries(exe, needed_libraries_names)
○ step 3 : relocate all to-be-relocated symbols in the application and libraries
liba1.soexe liba2.so libb1.so libb2.so ...
libraries_list
liba1.so
sym_1
sym_n
...
if sym_1 is defined in lib: sym_1 = lib.find(sym)else: lib = lib->next;
Link the application and all libraries● find_libaries(exe, needed_libraries_names)
○ step 3 : relocate all to-be-relocated symbols in the application and libraries
liba1.soexe liba2.so libb1.so libb2.so ...
libraries_list
...
sym_1
sym_n
...
if sym_1 is defined in lib: sym_1 = lib.find(sym)else: lib = lib->next;
Link the application and all libraries● find_libaries(exe, needed_libraries_names)
○ step 3 : relocate all to-be-relocated symbols in the application and libraries
liba1.soexe liba2.so libb1.so libb2.so ...
libraries_list
...
sym_1
sym_n
...
if sym_1 is defined in lib: sym_1 = lib.find(sym)else: lib = lib->next;
It is DONE until all libraries are linked
Jump to the application’s entry
/system/bin/linker
high
low
Memory space
executable
liba1.so
liba2.so
libb1.so
...
● jump to executable’s _start.
The executable is loaded successfully● And start to execute
/system/bin/linker
high
low
Memory space
liba1.so
liba2.so
libb1.so
...
.text section
_start:….….
executable
Bionic linker linking & loading flow● Dynamic linking flow● Dynamic loading flow
__linker_init_post_relocation
Dynamic linking
dlopen_ext
do_dlopen
find_library
find_libraries
find_library_internal
load_library
Dynamic loading
...load all libraries…relocate all symbols
Native hook mechanismModified codes are mainly in two parts
● Load hooking libraries in find_libraries()○ Init native_hook_table○ Look up native_hook_table○ Load hooking_library
● Replace hooked_symbol with hooking_symbol in soinfo_do_lookup()○ Look up native_hook_table○ Replace every hooked_symbol in hooked_library with hooking_symbol in
hooking_library
Native hook file formatin /system/nh_file.txt
< hooked_lib_name:hooked_symbol:hooking_lib_name:hooking_symbol >
System flow
hooking lib
nh_file
ROM
/system/bin/linker
__linker_init_post_relocation
find_libraries
init native_hook_table
look up native hook table
soinfo_do_lookup
look up native hook table
replace hooked symbol with hooking symbol
NewProcess
load hooking library
Load hooking libraries
linkerexe
liba1.so
liba2.so Loaded NotLoaded
p.s.
liba1.so:hi:libhooking.so:ha...
Native Hook Table
Load hooking libraries
linkerexe
liba1.so
liba2.so Loaded NotLoaded
p.s.
liba1.so:hi:libhooking.so:ha...
Native Hook Table
0. load liba1.so
Load hooking libraries
linkerexe
liba1.so
liba2.so Loaded NotLoaded
p.s.
liba1.so:hi:libhooking.so:ha...
Native Hook Table
1. look up the native hook table
HOOKED LIB “liba1.so” FOUND
Load hooking libraries
linkerexe
liba1.so
liba2.so Loaded NotLoaded
p.s.
liba1.so:hi:libhooking.so:ha...
Native Hook Table
2. load libhooking.so
libhooking.so
Replace hooked_symbol with hooking_symbol
liba1.soexe liba2.so libhooking.so
libraries_list
liba1.so:hi:libhooking.so:ha...
Native Hook Table
exe
hi
hi halinker
0. relocate symbol
Replace hooked_symbol with hooking_symbol
liba1.soexe liba2.so libhooking.so
libraries_list
liba1.so:hi:libhooking.so:ha...
Native Hook Table
exehi ha
linker
NOT FOUNDhi
Replace hooked_symbol with hooking_symbol
liba1.soexe liba2.so libhooking.so
libraries_list
liba1.so:hi:libhooking.so:ha...
Native Hook Table
exehi ha
linker
FOUNDhi
Replace hooked_symbol with hooking_symbol
liba1.soexe liba2.so libhooking.so
libraries_list
liba1.so:hi:libhooking.so:ha...
Native Hook Table
exehi ha
linker
FOUNDhi
1. look up native hook table
liba1.so:hi is to be hooked
Replace hooked_symbol with hooking_symbol
liba1.soexe liba2.so libhooking.so
libraries_list
liba1.so:hi:libhooking.so:ha...
Native Hook Table
exehi ha
linker
hi
1. look up native hook table
2. find libhooking.so:ha
Replace hooked_symbol with hooking_symbol
liba1.soexe liba2.so libhooking.so
libraries_list
liba1.so:hi:libhooking.so:ha...
Native Hook Table
exehi ha
linker
ha
3. relocate hooked_symbol “hi” with the hooking_symbol “ha”
// in libnativehook.so
#include “native_hook.h”
void* find_lib_symbol(char* lib_name, char* symbol){
// Using dl_iterate_phdr() to get the symbol’s address// in the loaded library whose name is lib_name.…return ptr_to_symbol;
}
Before/After hook SDK
How find_lib_symbol() works ?With the following facts, we can get the hooked_symbol in hooked_library with dl_iterate_phdr(callback, void* data)
● hooked_lib is loaded in the memory● dl_iterate_phdr()iterates all loaded libraries in the process, and get each
library’s program header and base address.● With library’s program header, we can get .dynamic segment, and therefore we get
.dynstr and .dynsym section● With .dynsym and .dynstr, we can find the offset of hooked_symbol in hooked_lib.● hooked_symbol_addr = base address + offset
// in libmine.so#include “native_hook.h”
double my_sin(double x){
char hooked_lib[] = "/system/lib/libm.so";char hooked_symbol[] = "sin";double (*hooked_sin)(double) = find_lib_symbol(hooked_lib, hooked_symbol);
/* before hook : you can do something before calling hooked_func */
double result = hooked_sin(x);/*
after hook : you can do something after calling hooked_func */
result += 5566;
return result;}
After hook example
After hook example// in main.c
#include <math.h>#include <stdio.h>#define PI 3.14159265
int main(void){
double angle = 30.0;double result = sin((angle * PI) /
180);printf(“sin(%lf) = %lf\n”, angle,
result);return 0;
}
libm.so:sin:libmine.so:my_sin...
Native Hook Table
$ ./mainsin(30.000000) = 5566.500000
double my_sin(double x){
char hooked_lib[] = "/system/lib/libm.so";char hooked_symbol[] = "sin";static void* cache_ptr = NULL;double (*hooked_sin)(double) = NULL;if (cache_ptr) {
hooked_sin = cache_ptr;} else {
hooked_sin = find_lib_symbol(hooked_lib, hooked_symbol);}if (hooked_sin) {
cache_ptr = (void*)hooked_sin;}double result = hooked_sin(x);result += 5566;return result;
}
Before/After hook with cache
Experiment1,000 100,000 1,000,000 10,000,000
Baseline 0.10 0.14 0.52 4.07
Normal hook 0.20 0.23 0.60 4.15
Before/After hook without cache 0.25 1.9 17.12 169.03
Before/After hook with cache 0.22 0.24 0.69 4.77
iterations
Experiment169.03
Applications● Profiling● Boosting apps performance● Security sandbox
Profiling
Target function
Before hook
After hook
● Input Distribution Analysis
● Function call Analysis
● Output Analysis
● Hook functions that affect the performance of applications in Android
● Scenario○ Functions in libm.so are not good enough for some special
purpose, we can hook the function with the optimized one.
Boosting apps performance
libm_opt.so
optimized_sin:...
libbenchmark.so
getScore:…call <sin>
App
libm.so
sin:...
JNI
libm_opt.so
optimized_sin:...
libbenchmark.so
getScore:…call <sin>
App
libm.so
sin:...
JNI
Replace ‘sin’ with ‘optimized_sin’
Security sandbox● Use “before hook” to hook the open()in libc● Examine the filename and other parameters in advance
○ If the to-be-written file is a critical file, we let the app open another file to write without consciousness.
Security sandbox
f = open(“/data/critical.txt”, ‘w’);...
modifying critical.txt ...
...
App
Sandbox
Security sandbox
f = open(“/data/critical.txt”, ‘w’);...
modifying critical.txt ...
...
App
Sandbox
/data/critical.txt should not be modified.
Security sandbox
f = open(“/data/critical.txt”, ‘w’);...
modifying critical.txt ...
...
App
Sandbox
f = open(“/data/another.txt”, ‘w’);
In the sandbox, app is deceived to write to “/data/another.txt” instead of
“/data/critical.txt”.
Security sandbox
App
Sandbox
f = open(“/data/another.txt”, ‘w’);
f = open(“/data/another.txt”, ‘w’);...
modifying another.txt ...
...
● Provide more easy-to-use API for Native Hook in Android○ Native Hook SDK
Future works
● Completely integrate Native Hook into Android Dynamic Framework○ Provide hooking between Java method and native functions.
Future works
Integrated Hook Table
liba.so:funca:libb.so:funcb # hook native to nativeclassA:methoda:classB:methodb # hook java to javaclassA:methoda:libb.so:funcb # hook java to nativelibb.so:funcb:classA:methoda # hook native to java
...
Conclusion● Native Hook mechanism is a strong and useful framework in
Android allowing developers to replace native functions at runtime without modifying the existing functions.
● Native Hook is more powerful than Java method hook mechanisms because it is implemented in Bionic Linker.
● With Before/After hook mechanism, you can do whatever you want before/after any existing function.
● With Native Hook enabled, it suffers only little overhead to load nh_file and hooking libraries.
Q & A
Thank you for your listening
Backup slides
void* find_lib_symbol(char* lib_name, char* symbol){
// Using dl_iterate_phdr() to get the symbol’s address// in the loaded library whose name is lib_name.static void* unordered_map<std::string, void*> cache = nullptr;std::string lib_symbol = std::string(lib_name) + symbol;if (cache) {
unordered_map<std::string, void*>::iterator it = cache.find(lib_symbol);if (it != cache.end()) {
return it->second;}
}…// find ptr_to_symbolif (ptr_to_symbol) {
cache[lib_symbol] = ptr_to_symbol;}return ptr_to_symbol;
}
Before/After hook with cache in find_lib_symbol
Replace hooked_symbol with hooking_symbol
liba1.soexe liba2.so libhooking.so
libraries_list
liba1.so:hi:libhooking.so:ha...
Native Hook Table
exehi ha
linker
FOUNDhi
1. look up native hook table
liba1.so:hi is to be hooked
2. find libhooking.so:ha