这里以HOOK程序启动后调用天天星联盟为例,下面是2014年4月30日抠出来的天天星联盟插件APK代码:
测试只要把so加载和调用的部分去掉即可,反编译后去掉UniversalUI.smali的以下代码:
但是这个仅限于在当前进程中,一旦注入到其他进程中,这种方式就不行,可以考虑其他通信方式。
主要目的是HOOK onCreate函数,这样当启动类的
onCreate被调用时首先是进入到我们的hook函数:
packagecom.xxAssistant.UI;importandroid.app.Activity;importandroid.content.Context;importandroid.util.Log;importandroid.widget.RelativeLayout;importandroid.widget.Toast;publicclassUniversalUIextendsRelativeLayout {privatestaticString mSoPath;privatestaticUniversalUI me =null;privateActivity mActivity;privateContext mContext;privatebooleanmShow;privateUniversalUI(Activity paramActivity) {super(paramActivity);this.mContext = paramActivity;this.mActivity = paramActivity;this.mShow =false; }publicstaticvoidinit(Activity paramActivity, String paramString) {if(me ==null);for(UniversalUI localUniversalUI =newUniversalUI(paramActivity); ; localUniversalUI = me) { me = localUniversalUI; mSoPath = paramString; me.show();return; } }privatenativevoidsetqudao();privatevoidshow() {if(!this.mShow) {this.mShow =true; Log.d("native", "叉叉辅助已成功装载"); Toast.makeText(this.mContext, "叉叉辅助已成功装载", 1).show(); System.load(mSoPath);xxdohook();} }privatenativevoidxxdohook();}
.
line
40
sget
-
object
v0
,
Lcom
/
xxAssistant
/
UI
/
UniversalUI;
->
mSoPath:Ljava
/
lang
/
String;
invoke
-
static
{
v0
},
Ljava
/
lang
/
System;
->
load
(
Ljava
/
lang
/
String;
)
V
.
line
41
invoke
-
direct
{
p0
},
Lcom
/
xxAssistant
/
UI
/
UniversalUI;
->
xxdohook
()
V
以及:
.
method
private
native
xxdohook
()
V
.
end method
回编译后再次确认下:
packagecom.xxAssistant.UI;importandroid.app.Activity;importandroid.content.Context;importandroid.util.Log;importandroid.widget.RelativeLayout;importandroid.widget.Toast;publicclassUniversalUIextendsRelativeLayout {privatestaticString mSoPath;privatestaticUniversalUI me =null;privateActivity mActivity;privateContext mContext;privatebooleanmShow;privateUniversalUI(Activity paramActivity) {super(paramActivity);this.mContext = paramActivity;this.mActivity = paramActivity;this.mShow =false; }publicstaticvoidinit(Activity paramActivity, String paramString) {if(me ==null);for(UniversalUI localUniversalUI =newUniversalUI(paramActivity); ; localUniversalUI = me) { me = localUniversalUI; mSoPath = paramString; me.show();return; } }privatenativevoidsetqudao();privatevoidshow() {if(!this.mShow) {this.mShow =true; Log.d("native", "叉叉辅助已成功装载"); Toast.makeText(this.mContext, "叉叉辅助已成功装载", 1).show(); } } }
最后记得签名APK,然后把修改后的APK复制到项目工程的assets目录下:
运行时动态将这两个资源文件复制到app_plugin目录下,叉叉助手是把不同的插件以id分别放到app_plugin下的子目录中,我们仅作测试不再创建子目录。
运行效果:
点击“浏览”按钮选择要HOOK的APP:
选择后程序会自动分析其包名和启动Acitivity,然后点击“HOOK”按钮开始注入:
注入成功后便可以点击“启动”按钮运行刚刚选择的APP了:
程序界面上显示了叉叉助手插件TOAST出的信息。
由于界面上设置了动态可配置的包名,随意选择另外一个APP并启动:
也就是说这种方法具有通用性。
LOG信息输出到界面中也是为了方便查看,不用每次都去用LOGCAT等工具查看,原理是底层通过反射调用java层函数:
voidLOGMSG(constchar*lpszText) { LOGD("%s", lpszText);if( g_context!=NULL ) { jclass jclazz= g_env->GetObjectClass(g_context); jmethodID logMsg= g_env->GetMethodID(jclazz,"logMsg","(Ljava/lang/String;)V");if( logMsg ) {//LOGD("found: logMsg");jstring jstr =str2jstring(g_env, lpszText, strlen(lpszText), NULL);//LOGD("CallObjectMethod 1");g_env->CallVoidMethod(g_context, logMsg, jstr);//LOGD("CallObjectMethod 2");} } }
执行流程:
injectso把middle注入到父进程,middle启动时调用该函数。
/*
由于libandroidloader.so使用动态方式加载libsubstratedvm.so并获取MSJavaHookMethod进行调用会崩溃,
一定需要隐式链接:
LOCAL_LDLIBS := -L$(LOCAL_PATH) -llog -ldl libsubstratedvm.so
所以libandroidloader.so在加载前进程模块中一定要存在libsubstratedvm.so和libsubstrate.so,
因此需要先注入一个跳板模块(这里就是middle)来完成对以上两个so的加载,然后再加载libandroidloader.so,
这样libandroidloader.so在启动时它所需要的两个so都已经被加载完成了,就不会出现崩溃问题了。
*/
HOOK android/app/ActivityThread::handleBindApplication,这样当APP启动时就能触发到回调函数。
OnCallback_handleBindApplicaton:
//参数二的形式为:android.app.ActivityThread@41683ec8//参数三的形式为:AppBindData{appInfo=ApplicationInfo{4178ead0 com.huawei.ChnUnicomAutoReg}}//因此可以通过参数三的包名进行过滤
staticvoidOnCallback_handleBindApplicaton(JNIEnv *jni, jobject jthis, jobject appBindData) { LOGD("[%s] begin", __FUNCTION__);stringstrName; strName = getObjectClassName(jni, appBindData);if( (int)strName.size() >0) { LOGD("[%s] class: %s", __FUNCTION__, strName.c_str()); }//读取hook配置信息,这里每次都要读,考虑到配置文件会发生变动boolbSuccess = LoadConfig(g_cfg);if( bSuccess==false) { LOGE("[%s] load config failed", __FUNCTION__); }else{//根据包名过滤,hook指定的apk//string::size_type position = strName.npos;if( strName.find(g_cfg.strHostPackage)!=strName.npos ) {//是目标appLOGD("[%s] target apk running: %s", __FUNCTION__, g_cfg.strHostPackage.c_str());////hook目标的activity的类加载以及onCreate//MSJavaHookClassLoad(jni, g_cfg.strHostActivity.c_str(), &OnCallback_JavaClassLoad, NULL);////上下两块独立互不干扰,可以同时使用也可分开使用////简易脱壳机:可脱我们自己的壳,梆梆加固,爱加密,360加固保等//void* handle=dlopen("libdvm.so", RTLD_NOW); LOGD("handle:%p", handle); JNINativeMethod *dvm_dalvik_system_DexFile = (JNINativeMethod*)dlsym(handle,"dvm_dalvik_system_DexFile"); dvmCreateCstrFromString = (dvmCreateCstrFromStringPtr)dlsym(handle,"_Z23dvmCreateCstrFromStringPK12StringObject"); LOGD("dvmCreateCstrFromString:%p", dvmCreateCstrFromString); dvmRawDexFileOpen = (dvmRawDexFileOpenPtr)dlsym(handle,"_Z17dvmRawDexFileOpenPKcS0_PP10RawDexFileb"); dvmJarFileOpen = (dvmRawDexFileOpenPtr)dlsym(handle,"_Z14dvmJarFileOpenPKcS0_PP7JarFileb"); LOGD("dvmRawDexFileOpen:%p", dvmRawDexFileOpen); LOGD("dvmJarFileOpen:%p", dvmJarFileOpen);if( dvmRawDexFileOpen ) { MSHookFunction(dvmRawDexFileOpen, newdvmRawDexFileOpen, &olddvmRawDexFileOpen); }if( dvmJarFileOpen ) { MSHookFunction(dvmJarFileOpen, newdvmJarFileOpen, &olddvmJarFileOpen); }//dvmDexFileOpenPartial = (dvmDexFileOpenPartialPtr)dlsym(handle, "_Z21dvmDexFileOpenPartialPKviPP6DvmDex");//LOGD("dvmDexFileOpenPartial: %p", dvmDexFileOpenPartial);//if ( dvmDexFileOpenPartial ) {//MSHookFunction(dvmDexFileOpenPartial, newdvmDexFileOpenPartial, &olddvmDexFileOpenPartial);//}//dvmDexFileOpenPartial会调用dexFileParse,因此只需要hookdexFileParse即可。dexFileParse = (dexFileParsePtr)dlsym(handle,"_Z12dexFileParsePKhji"); LOGD("dexFileParse: %p", dexFileParse);if( dexFileParse ) { MSHookFunction(dexFileParse, newdexFileParse, &olddexFileParse); }if(dvm_dalvik_system_DexFile){ lookup(dvm_dalvik_system_DexFile,"openDexFile","([B)I", &openDexFile); LOGD("openDexFile:%p",openDexFile); MSHookFunction(openDexFile, newDalvik_dalvik_system_DexFile_openDexFile_bytearray, &oldDalvik_dalvik_system_DexFile_openDexFile_bytearray); lookup(dvm_dalvik_system_DexFile,"openDexFile","(Ljava/lang/String;Ljava/lang/String;I)I", &openDexFile); LOGD("openDexFile:%p",openDexFile); MSHookFunction(openDexFile, newDalvik_dalvik_system_DexFile_openDexFile, &oldDalvik_dalvik_system_DexFile_openDexFile); }//} }//继续原函数处理(*old_handleBindApplication)(jni, jthis, appBindData); LOGD("[%s] end", __FUNCTION__); }
当目标的启动类被加载时触发回调
OnCallback_JavaClassLoad
:
//当类被加载时触发的回调函数staticvoidOnCallback_JavaClassLoad(JNIEnv *jni, jclass _class,void*arg) { LOGD("[%s] begin", __FUNCTION__);if(!g_cfg.IsValid()) { LOGE("[%s] hook config is not valid", __FUNCTION__);return; } jmethodID onCreate= jni->GetMethodID(_class,"onCreate","(Landroid/os/Bundle;)V");if(onCreate ==NULL) { LOGE("[%s] \"onCreate\" not found in class: %s", __FUNCTION__, g_cfg.strHostActivity.c_str());return; } old_onCreate=NULL; LOGD("[%s] hook \"onCreate\"", __FUNCTION__); MSJavaHookMethod(jni, _class, onCreate, (void*) (&OnCallback_ClassOnCreate), (void**) (&old_onCreate));if( old_onCreate==NULL ) { LOGE("[%s] old_onCreate returned NULL", __FUNCTION__); } LOGD("[%s] end", __FUNCTION__); }
//当类的onCreate调用时触发回调,动态加载插件apk并调用插件函数/*ClassLoader localClassLoader = ClassLoader.getSystemClassLoader(); DexClassLoader localDexClassLoader = new DexClassLoader(dexpath, dexoutputpath, null, localClassLoader); 动态加载时参数dexOutputDir为/data/data/目标APK包名/app_dex,获取方法: getDir("dex",0).getAbsolutePath() 其他路径会失败,错误:optimizedDirectory not readable/writable:+路径; 参见:http://www.cnblogs.com/LittleRedPoint/p/3429709.html由于app_dex目录默认是不存在的,需要动态创建以及修改文件夹权限,比较繁琐,这里直接使用cache目录。 old_onCreate在最开始调用了,所以后面的函数都可以不限制地调用return*/staticvoidOnCallback_ClassOnCreate(JNIEnv *jni, jobject activity, jobject bundle) {stringstrName; strName=getObjectClassName(jni, activity); LOGD("[%s] begin: %s::OnCreate", __FUNCTION__, strName.c_str()); (*old_onCreate)(jni, activity, bundle); LOGD("[%s] dynamic load plug apk", __FUNCTION__);if(!g_cfg.IsValid()){ LOGD("[%s] config invalid", __FUNCTION__);return; } jclass ClassLoader= jni->FindClass("java/lang/ClassLoader");if( ClassLoader==NULL ) { LOGE("[%s] not found: java/lang/ClassLoader", __FUNCTION__);return; } jmethodID getSystemClassLoader= jni->GetStaticMethodID(ClassLoader,"getSystemClassLoader","()Ljava/lang/ClassLoader;");if( getSystemClassLoader==NULL ) { LOGE("[%s] not found: getSystemClassLoader", __FUNCTION__);return; } jobject systemLoader= jni->CallStaticObjectMethod(ClassLoader, getSystemClassLoader);if( systemLoader==NULL ) { LOGE("[%s] getSystemClassLoader() return NULL", __FUNCTION__);return; } jclass dexLoaderClass= jni->FindClass("dalvik/system/DexClassLoader");if( dexLoaderClass==NULL ) { LOGE("[%s] not found: dalvik/system/DexClassLoader", __FUNCTION__);return; } jmethodID initDexLoaderMethod= jni->GetMethodID(dexLoaderClass,"<init>","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V");if( initDexLoaderMethod==NULL ) { LOGE("[%s] not found: dalvik/system/DexClassLoader::<init>", __FUNCTION__);return; }//注意这里必须是当前进程的目录,不能跨进程目录读写stringapp_dex ="/data/data/"+ g_cfg.strHostPackage +"/cache";//LOGD("app_dex is %s", app_dex.c_str());jstring apkPath = jni->NewStringUTF(g_cfg.strPlugApkPath.c_str()); jstring dexOutputDir= jni->NewStringUTF(app_dex.c_str()); jobject dexClassLoader= jni->NewObject(dexLoaderClass, initDexLoaderMethod, apkPath, dexOutputDir, NULL, systemLoader);//jni->DeleteLocalRef(apkPath);//jni->DeleteLocalRef(dexOutputDir);if( dexClassLoader==NULL ) { LOGE("[%s] new DexClassLoader(...) return NULL", __FUNCTION__);return; } jmethodID loadClass= jni->GetMethodID(dexLoaderClass,"loadClass","(Ljava/lang/String;)Ljava/lang/Class;");if( loadClass==NULL ) { LOGE("[%s] not found: dalvik/system/DexClassLoader::loadClass", __FUNCTION__);return; } jstring javaClassName= jni->NewStringUTF(g_cfg.strPlugActivity.c_str()); jclass plugClass= (jclass)jni->CallObjectMethod(dexClassLoader, loadClass, javaClassName);//jni->DeleteLocalRef(apkPath);if( plugClass==NULL ) { LOGE("[%s] DexClassLoader::loadClass not found class: %s", __FUNCTION__, g_cfg.strPlugActivity.c_str());return; }//调用插件的静态函数init,原型:public static void init(Activity activity, String soPath)jmethodID plugUIinit = jni->GetStaticMethodID(plugClass,"init","(Landroid/app/Activity;Ljava/lang/String;)V");if( plugUIinit==NULL ) { LOGE("[%s] not found \"init\" in plug activity: public static void init(Activity activity, String soPath)", __FUNCTION__); }else{ LOGD("[%s] invoke %s::init", __FUNCTION__, g_cfg.strPlugActivity.c_str()); jstring soPath= jni->NewStringUTF(g_cfg.strPlugSoPath.c_str());jni->CallStaticVoidMethod(plugClass, plugUIinit, activity, soPath);//jni->DeleteLocalRef(soPath);} LOGD("[%s] end", __FUNCTION__); }
其实主要目的就是(
jni
->
CallStaticVoidMethod(plugClass, plugUIinit, activity, soPath);
)调用插件某类的静态函数,传给两个参数。
但是实现上确实比较繁琐。
运行效果就是开头的几张图。
引申到脱壳机:在外壳加载原始apk的过程中,会调用某些系统api,只要hook拦截下来并dump出相应的数据即可。
再回到OnCallback_handleBindApplicaton函数分析下半部分。
涉及到的:
ODEX转DEX的操作(参考
http://blog.csdn.net/asmcvc/article/details/39078421):
1、ODEX反编译:
java -jar baksmali.jar -x a.odex
2、反编译结果再打包成DEX:
java -Xmx512M -jar smali.jar out -o classes.dex
3、DEX转JAR看源码。
2、反编译结果再打包成DEX:
java -Xmx512M -jar smali.jar out -o classes.dex
3、DEX转JAR看源码。
如果出现诸如以下错误:
odex转dex遇到的异常 Cannot locate boot class path file /system/framework/core.odex
请按照上面链接中的解决办法解决。
爱加密:
staticcharchCount =0x30;//dvmDexFileOpenPartial会调用dexFileParse,因此只需要HOOK dexFileParse即可。intnewdvmDexFileOpenPartial(constvoid* addr,intlen,void* ppDvmDex) { LOGD("[%s] begin", __FUNCTION__);intnret = olddvmDexFileOpenPartial(addr, len, ppDvmDex);stringdexFileName ="/data/data/"+ g_cfg.strHostPackage +"/cache/o.odex"; dexFileName.append(1, ++chCount);if( saveFile(addr, len, dexFileName.c_str()) ) {intnret = chmod(dexFileName.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); LOGD("[%s] dumped file: %s chmod return: %d error: %s", __FUNCTION__, dexFileName.c_str(), nret, dlerror()); } LOGD("[%s] end", __FUNCTION__);returnnret; }
梆梆加固:
[newDalvik_dalvik_system_DexFile_openDexFile] sourceName: /data/data/com.mxy/.cache/
classes.jaroutputName: /data/data/com.mxy/.cache/
classes.dex
[newdvmJarFileOpen] fileName: /data/data/com.mxy/.cache/classes.jar odexOutputName: /data/data/com.mxy/.cache/classes.dex
[newdvmJarFileOpen] fileName: /data/data/com.mxy/.cache/classes.jar odexOutputName: /data/data/com.mxy/.cache/classes.dex
[newdvmRawDexFileOpen] fileName: /data/data/com.example.helloapplication/app_bangcleplugin/container.dex odexOutputName: /data/data/com.example.helloapplication/app_outdex.container.e8b06f9b6317e8fc0c1ddeafd7f71664/container.dex
[newdvmRawDexFileOpen] fileName: /data/data/com.example.helloapplication/app_bangcleplugin/collector.dex odexOutputName: /data/data/com.example.helloapplication/app_outdex.collector.f961d3dbc359c93741d5c63d553c0f57/collector.dex
[newdvmRawDexFileOpen] fileName: /data/data/com.example.helloapplication/app_bangcleplugin/collector.dex odexOutputName: /data/data/com.example.helloapplication/app_outdex.collector.f961d3dbc359c93741d5c63d553c0f57/collector.dex
voidnewDalvik_dalvik_system_DexFile_openDexFile(constu4* args, JValue* pResult) { LOGD("[%s] begin", __FUNCTION__); oldDalvik_dalvik_system_DexFile_openDexFile(args, pResult); StringObject* sourceNameObj = (StringObject*) args[0]; StringObject* outputNameObj = (StringObject*) args[1];char* sourceName = NULL;char* outputName = NULL;if( dvmCreateCstrFromString!=NULL ) { sourceName = dvmCreateCstrFromString(sourceNameObj); outputName = dvmCreateCstrFromString(outputNameObj); LOGD("[%s] sourceName: %s outputName: %s", __FUNCTION__, sourceName, outputName);//复制备份if( strstr(sourceName,"classes.jar")!=NULL ) {stringdexFileName ="/data/data/"+ g_cfg.strHostPackage +"/cache/classes.jar"; dexFileName.append(1, ++chCount); LOGD("[%s] backup file to: %s", __FUNCTION__, dexFileName.c_str()); copyFile(sourceName, dexFileName.c_str()); chmod(dexFileName.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); LOGD("[%s] dumped file: %s", __FUNCTION__, dexFileName.c_str()); }/*else if ( strstr(sourceName, "collector.dex")!=NULL || strstr(sourceName, "container.dex")!=NULL ) { string dexFileName = "/data/data/" + g_cfg.strHostPackage + "/cache/out.dex"; dexFileName.append(1, ++chCount); LOGD("[%s] backup file to: %s", __FUNCTION__, dexFileName.c_str()); copyFile(sourceName, dexFileName.c_str()); }*/} LOGD("[%s] end", __FUNCTION__); }
梆梆加固的被Dalvik_dalvik_system_DexFile_openDexFile拦截到时判断输入参数,如果sourceName包含classes.jar,则复制备份出来,这个classes.jar其实是一个zip包,里面只有一个文件:classes.dex,classes.dex可以使用三方工具转换成jar文件,就是目标的DEX。collector.dex和collector.dex备份出来后发现是插件。
360加固保:
360的需要HOOK函数dexFileParse,可以DUMP出ODEX,但是直接使用baksmali.jar操作会出错:
Error occurred while loading boot class path files. Aborting.
org.jf.util.ExceptionWithContext: Cannot locate boot class path file /system/fra
mework/core.odex
at org.jf.dexlib2.analysis.ClassPath.loadClassPathEntry(ClassPath.java:2
17)
at org.jf.dexlib2.analysis.ClassPath.fromClassPath(ClassPath.java:161)
at org.jf.baksmali.baksmali.disassembleDexFile(baksmali.java:67)
at org.jf.baksmali.main.main(main.java:280)
org.jf.util.ExceptionWithContext: Cannot locate boot class path file /system/fra
mework/core.odex
at org.jf.dexlib2.analysis.ClassPath.loadClassPathEntry(ClassPath.java:2
17)
at org.jf.dexlib2.analysis.ClassPath.fromClassPath(ClassPath.java:161)
at org.jf.baksmali.baksmali.disassembleDexFile(baksmali.java:67)
at org.jf.baksmali.main.main(main.java:280)
这个应该需要再额外设置一些东西,此处不再研究。综合一下,由于函数dvmDexFileOpenPartial内部也要调用dexFileParse,因此爱加密和360加固的脱壳方法一致,统一HOOK函数dexFileParse:
//dvmDexFileOpenPartial会调用dexFileParse,因此只需要HOOK dexFileParse即可。void* newdexFileParse(constu1* data, size_t length,intflags) { LOGD("[%s] begin", __FUNCTION__);void* nret = olddexFileParse(data, length, flags);stringdexFileName ="/data/data/"+ g_cfg.strHostPackage +"/cache/o.odex"; dexFileName.append(1, ++chCount);if( saveFile(data, length, dexFileName.c_str()) ) {intnret = chmod(dexFileName.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); LOGD("[%s] dumped file: %s chmod return: %d error: %s", __FUNCTION__, dexFileName.c_str(), nret, dlerror()); } LOGD("[%s] end", __FUNCTION__);returnnret; }
我们自己的加壳程序:
//hook内存加载odex的函数voidnewDalvik_dalvik_system_DexFile_openDexFile_bytearray(constu4* args, JValue* pResult) { LOGD("[%s] begin", __FUNCTION__); ArrayObject* fileContentsObj = (ArrayObject*) args[0];stringdexFileName ="/data/data/"+ g_cfg.strHostPackage +"/cache/out.odex"; dexFileName.append(1, ++chCount);if( saveFile(fileContentsObj->contents, fileContentsObj->length, dexFileName.c_str()) ) { chmod(dexFileName.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); LOGD("[%s] dumped file: %s", __FUNCTION__, dexFileName.c_str()); } LOGD("[%s] end", __FUNCTION__); oldDalvik_dalvik_system_DexFile_openDexFile_bytearray(args, pResult); }
其他应用场景:
API Monitor
APKScan可以基于此原理
代码已递交SVN,大家可以根据需要完善或修改。
版权声明:本文为博主原创文章,未经博主允许不得转载。