本文轉自http://www.9hao.info/pages/2014/08/android-soke-ru-kou-q
前言
開年來開始接觸一些加固樣本,基本都對了so進行了處理,拖入ida一看,要么沒有 JNI_OnLoad ,要么 JNI_OnLoad 匯編代碼羞澀難懂,讓人無法下手。 JNI_OnLoad 是真正入口么?
先看看幾個文檔
1 摘自屬性服務一節(《深入理解Android卷1》)
利用gcc的constructor屬性,這個屬性指明了一個__libc_prenit函數(這個函數內部就將完成共享內存到本地進程的映射工作)。用法:當bionic libc庫被加載時,將自動調用__libc_prenit函數。這樣在bionic libc動態庫被裝載時,系統屬性緩沖區地址就被確定了,后續的API調用就能找對位置了。 /* We flag the __libc_preinit function as a constructor to ensure * that its address is listed in libc.so's .init_array section. * This ensures that the function is called by the dynamic linker * as soon as the shared library is loaded. */ //constructor屬性指示加載器加載該庫之后,首先調用__libc_prenit函數。這一點和windows上的動態庫的DllMain函數類似 void __attribute__((constructor)) __libc_prenit(void);
從英文說明里面提到到.init_array section,我們可以搜索一下這一節的說明
2 .init_array section
.init_array contains pointers to blocks of code that need to be executed when an application is being initialized (before main() is called). It is used for a number of things, but the primary use is in C++ for running static constructors; a secondary use that is sometimes used is to initialize IO systems in the C library. If you are not using C++ you may (depending on your C library) be able to live without it entirely; but you’d need to hack your startup code to deal with this. .init_array probably ends up in ram because its marked read/write — that happens because in a dynamic linking environment the dynamic linker has to fix up all the pointers it contains before it can be used. In a static environment you might be able to get away with forcing it into a read-only section. 來源: <http://blog.sina.com.cn/s/blog_a9303fd901019kvq.html>
3 摘自dlopen小結(《程序員的自我修養》)
動態連接器在加載模塊時,會執行".init"段的代碼,用以完成模塊的初始化工作,dlopen的加載過程基本跟動態連接器一致,在完成裝載、映射和重定向以后,就會執行".init"段的代碼然后返回
看完這個3段資料,我們可以知道在系統加載so,在完成裝載、映射和重定向以后,就首先執行.init和.init_array段的代碼.
探本溯源,在源碼中追蹤
我們先從System.loadLibrary ->Runtime.loadLibrary
public void loadLibrary(String libName) { loadLibrary(libName, VMStack.getCallingClassLoader()); } /* * Loads and links a library without security checks. */ void loadLibrary(String libraryName, ClassLoader loader) { 代碼略... String error = nativeLoad(filename, loader); 代碼略... }
-> nativeLoad
static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args, JValue* pResult) { 代碼略... StringObject* fileNameObj = (StringObject*) args[0]; success = dvmLoadNativeCode(fileName, classLoader, &reason); 代碼略... }
來源: <http://androidxref.com/4.1.2/xref/dalvik/vm/native/java_lang_Runtime.cpp#72>
bool dvmLoadNativeCode(const char* pathName, Object* classLoader, char** detail) { 代碼略... handle = dlopen(pathName, RTLD_LAZY); 代碼略... vonLoad = dlsym(handle, "JNI_OnLoad"); if (vonLoad == NULL) { ALOGD("No JNI_OnLoad found in %s %p, skipping init", pathName, classLoader); } else { /* * Call JNI_OnLoad. We have to override the current class * loader, which will always be "null" since the stuff at the * top of the stack is around Runtime.loadLibrary(). (See * the comments in the JNI FindClass function.) */ OnLoadFunc func = (OnLoadFunc)vonLoad; Object* prevOverride = self->classLoaderOverride; self->classLoaderOverride = classLoader; oldStatus = dvmChangeStatus(self, THREAD_NATIVE); if (gDvm.verboseJni) { ALOGI("[Calling JNI_OnLoad for \"%s\"]", pathName); } version = (*func)(gDvmJni.jniVm, NULL); dvmChangeStatus(self, oldStatus); self->classLoaderOverride = prevOverride; } 代碼略... }
來源: <http://androidxref.com/4.1.2/xref/dalvik/vm/Native.cpp#318>
通過dvmLoadNativeCode函數我們知道系統用dlopen加載so完成后,會查看有沒有JNI_OnLoad函數,有的話就調用.
我們再到dlopen函數探個究竟:
void *dlopen(const char *filename, int flag) { soinfo *ret; pthread_mutex_lock(&dl_lock); /*find_library 會判斷so是否已經加載,如果沒有加載,對so進行加載,完成一些初始化工作,有興趣的讀者可自行分析 */ ret = find_library(filename); if (unlikely(ret == NULL)) { set_dlerror(DL_ERR_CANNOT_LOAD_LIBRARY); } else { call_constructors_recursive(ret); ret->refcount++; } pthread_mutex_unlock(&dl_lock); return ret; }
void call_constructors_recursive(soinfo *si) { if (si->constructors_called) return; // Set this before actually calling the constructors, otherwise it doesn't // protect against recursive constructor calls. One simple example of // constructor recursion is the libc debug malloc, which is implemented in // libc_malloc_debug_leak.so: // 1. The program depends on libc, so libc's constructor is called here. // 2. The libc constructor calls dlopen() to load libc_malloc_debug_leak.so. // 3. dlopen() calls call_constructors_recursive() with the newly created // soinfo for libc_malloc_debug_leak.so. // 4. The debug so depends on libc, so call_constructors_recursive() is // called again with the libc soinfo. If it doesn't trigger the early- // out above, the libc constructor will be called again (recursively!). si->constructors_called = 1; if (si->flags & FLAG_EXE) { TRACE("[ %5d Calling preinit_array @ 0x%08x [%d] for '%s' ]\n", pid, (unsigned)si->preinit_array, si->preinit_array_count, si->name); call_array(si->preinit_array, si->preinit_array_count, 0); TRACE("[ %5d Done calling preinit_array for '%s' ]\n", pid, si->name); } else { if (si->preinit_array) { DL_ERR("%5d Shared library '%s' has a preinit_array table @ 0x%08x." " This is INVALID.", pid, si->name, (unsigned)si->preinit_array); } } 代碼略... if (si->init_func) { TRACE("[ %5d Calling init_func @ 0x%08x for '%s' ]\n", pid, (unsigned)si->init_func, si->name); si->init_func(); TRACE("[ %5d Done calling init_func for '%s' ]\n", pid, si->name); } if (si->init_array) { TRACE("[ %5d Calling init_array @ 0x%08x [%d] for '%s' ]\n", pid, (unsigned)si->init_array, si->init_array_count, si->name); //遍歷函數數組並執行 call_array(si->init_array, si->init_array_count, 0); TRACE("[ %5d Done calling init_array for '%s' ]\n", pid, si->name); //ps:看到這么多TRACE這么多調試信息,我們把調試開關打開,是不是能拿到諸多信息? } } 來源: <http://androidxref.com/4.1.2/xref/bionic/linker/linker.c#1519>
通過可以函數我們知道si->init_func和si->init_array存在的時候,會執行指向的函數
(不知道大家注意到么si->flags & FLAG_EXE時,還有si->preinit_array? 以后會不會有這方面的東西?)
再找下 si->init_func和si->init_array 的賦值
case DT_INIT: si->init_func = (void (*)(void))(si->base + *d); DEBUG("%5d %s constructors (init func) found at %p\n", pid, si->name, si->init_func); case DT_INIT_ARRAY: si->init_array = (unsigned *)(si->base + *d); DEBUG("%5d %s constructors (init_array) found at %p\n", pid, si->name, si->init_array); break;
DEBUG里面說明了constructors (init func)和constructors (init_array)。
我們再看看一份文檔Android Dynamic Linker Design Notes
DT_INIT Points to the address of an initialization function that must be called when the file is loaded. DT_INIT_ARRAY Points to an array of function addresses that must be called, in-order, to perform initialization. Some of the entries in the array can be 0 or -1, and should be ignored. Note: this is generally stored in a .init_array section
通過層層分析,我們很清楚知道了系統加載so,在完成裝載、映射和重定向以后,就首先執行.init和.init_array段的代碼.
前面有一篇文章我已經對so加殼進行簡單說明
把源碼的dlopen復制出來修改,在把自己so加載起來的時候 ,把自己內存里面某部分地址解密后,用自己的dlopen打開返回一個soinfo結構體 然后把當前soinfo結構體替換原來的soinfo結構體
小結
系統加載so,在完成裝載、映射和重定向以后,就首先執行.init和.init_array段的代碼,之后如果存在JNI_OnLoad 就調用該函數.我們要對一個so進行分析,需要先看看有沒有.init_array section和.init section,so加殼一般會在初始化函數進行脫殼操作。
如何在.init和.init_array段添加我們的函數
[1] 共享構造函數,在函數聲明時加上"__attribute__((constructor))"屬性 void __attribute__((constructor)) init_function(void); 對應有共享虛構函數,在程序exit()或者dlclose()返回前執行 void __attribute__((destructor)) fini_function(void); [2]c++ 靜態構造函數
在.init和.init_array下斷點
init_array 用ida可以看到, 可以對里面的函數數組下斷點 init ida有時沒識別出來,可用readelf查看入口點 xxx@xx:~$ readelf -a '/home/xxx/桌面/libsecexe.so' 0x00000010 (SYMBOLIC) 0x0 0x0000000c (INIT) 0x11401 0x00000019 (INIT_ARRAY) 0x28ca4 0x0000001b (INIT_ARRAYSZ) 8 (bytes) 0x0000001a (FINI_ARRAY) 0x28cac 0x0000001c (FINI_ARRAYSZ) 12 (bytes) 0x00000004 (HASH) 0xf4 我們看到 INIT 入口為 0x11401 (ps:有時你在0x11401是數據,你需要make code,由於對齊關系,要從0x11401+1開始) 樣本:梆梆 愛加密
參考:
[1] 《深入理解Android卷1》
[2] 《程序員的自我修養-鏈接、裝載與庫》
[3] android linker 淺析
[4] Android Dynamic Linker Design Notes
