Android漏洞之战——整体加壳原理和脱壳技巧详解

本文为看雪论坛优秀文章
看雪论坛作者ID:随风而行aa
一
前言
二
相关介绍
1.Android App启动流程
(1)Android系统启动流程


加载BootLoader --> 初始化内核 --> 启动init进程 --> init进程fork出Zygote进程 --> Zygote进程fork出SystemServer进程
· 系统启动时安装,没有安装界面· 第三方应用安装,有安装界面,也是我们最熟悉的方式· ADB命令安装,没有安装界面· 通过各类应用市场安装,没有安装界面

public static final IPackageManager main(Context context, Installer installer,boolean factoryTest, boolean onlyCore) {PackageManagerService m = new PackageManagerService(context, installer, factoryTest, onlyCore);ServiceManager.addService("package", m);return m;}

(2)App启动流程

(3)ActivityThread启动流程

public void handleMessage(Message msg) {****case BIND_APPLICATION:Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");AppBindData data = (AppBindData)msg.obj;handleBindApplication(data);Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);break;****}



① 完成了Application的实例化;
② 并调用Application.attach()函数。



2.整体加壳原理详解
(1)整体加壳原理




① BootClassLoader加载系统核心库。
② PathClassLoader加载APP自身dex。
③ 进入APP自身组件,解析AndroidManifest.xml,然后查找Application代理。
④ 调用声明Application的attachBaseContext()对源程序进行动态加载或解密。
⑤ 调用声明Application的onCreate()对源程序进行动态加载或解密。
⑥ 进入MainActivity中的attachBaseContext(),然后进入onCreate()函数,执行源程序代码。
(2)类加载器的修正


① 获取ActivityThread实例;
② 通过反射获取类加载器;
③ 获取LoadedApk;
④ 获取mClassLoader系统类加载器;
⑤ 替换自定义类加载器为系统类加载器。
public static void replaceClassLoader(Context context,ClassLoader dexClassLoader){ClassLoader pathClassLoader = MainActivity.class.getClassLoader();try {//1.获取ActivityThread实例Class ActivityThread = pathClassLoader.loadClass("android.app.ActivityThread");Method currentActivityThread = ActivityThread.getDeclaredMethod("currentActivityThread");Object activityThreadObj = currentActivityThread.invoke(null);//2.通过反射获得类加载器//final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();Field mPackagesField = ActivityThread.getDeclaredField("mPackages");mPackagesField.setAccessible(true);//3.拿到LoadedApkArrayMap mPackagesObj = (ArrayMap) mPackagesField.get(activityThreadObj);String packagename = context.getPackageName();WeakReference wr = (WeakReference) mPackagesObj.get(packagename);Object LoadApkObj = wr.get();//4.拿到mclassLoaderClass LoadedApkClass = pathClassLoader.loadClass("android.app.LoadedApk");Field mClassLoaderField = LoadedApkClass.getDeclaredField("mClassLoader");mClassLoaderField.setAccessible(true);Object mClassLoader =mClassLoaderField.get(LoadApkObj);Log.e("mClassLoader",mClassLoader.toString());//5.将系统组件ClassLoader给替换mClassLoaderField.set(LoadApkObj,dexClassLoader);}catch (ClassNotFoundException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (NoSuchFieldException e) {e.printStackTrace();}}
public static void replaceClassLoader(Context context, ClassLoader dexClassLoader){//将pathClassLoader父节点设置为DexClassLoaderClassLoader pathClassLoaderobj = context.getClassLoader();Class<ClassLoader> ClassLoaderClass = ClassLoader.class;try {Field parent = ClassLoaderClass.getDeclaredField("parent");parent.setAccessible(true);parent.set(pathClassLoaderobj,dexClassLoader);} catch (NoSuchFieldException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}
三
整体加壳案例实现
源程序加壳程序

2.编写壳程序
(1)准备工作


(2)编写代理类



(3)动态加载



四
脱壳点相关概念详解
1.Dex加载流程

DexPathList:该类主要用来查找Dex、SO库的路径,并这些路径整体呈一个数组;
Element:根据多路径的分隔符“;”将dexPath转换成File列表,记录所有的dexFile;
DexFile:用来描述Dex文件,Dex的加载以及Class的查找都是由该类调用它的native方法完成的。
/libcore/dalvik/src/main/java/dalvik/system/DexPathList.javapublic DexPathList(ClassLoader definingContext, String dexPath,String librarySearchPath, File optimizedDirectory) {**********************this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions, definingContext);**********************}
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions, ClassLoader loader) {**********************DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements);**********************}
private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,Element[] elements)throws IOException {if (optimizedDirectory == null) {return new DexFile(file, loader, elements);} else {String optimizedPath = optimizedPathFor(file, optimizedDirectory);return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);}}
static DexFile loadDex(String sourcePathName, String outputPathName,int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {return new DexFile(sourcePathName, outputPathName, flags, loader, elements);}
/libcore/dalvik/src/main/java/dalvik/system/DexFile.javaDexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {mCookie = openDexFile(fileName, null, 0, loader, elements);mInternalCookie = mCookie;mFileName = fileName;//System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);}
private static Object openDexFile(String sourceName, String outputName, int flags,ClassLoader loader, DexPathList.Element[] elements) throws IOException {// Use absolute paths to enable the use of relative paths when testing on host.return openDexFileNative(new File(sourceName).getAbsolutePath(),(outputName == null)? null: new File(outputName).getAbsolutePath(),flags,loader,elements);}

OpenDexFilesFromOat()MakeUpToDate()GenerateOatFileNoChecks()Dex2Oat()


/art/runtime/dex_file.ccstd::unique_ptr<const DexFile> DexFile::OpenFile(int fd,const std::string& location,bool verify,bool verify_checksum,std::string* error_msg) {**************************************std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(),map->Size(),location,dex_header->checksum_,kNoOatDexFile,verify,verify_checksum,error_msg);**************************************}

2.Dex2Oat编译流程

/art/runtime/exec_utils.ccbool Exec(std::vector<std::string>& arg_vector, std::string* error_msg) {int status = ExecAndReturnCode(arg_vector, error_msg);if (status != 0) {const std::string command_line(android::base::Join(arg_vector, ' '));*error_msg = StringPrintf("Failed execv(%s) because non-0 exit status",command_line.c_str());return false;}return true;}

/art/dex2oat/dex2oat.ccint main(int argc, char** argv) {int result = static_cast<int>(art::Dex2oat(argc, argv));if (!art::kIsDebugBuild && (RUNNING_ON_MEMORY_TOOL == 0)) {_exit(result);}return result;
/art/dex2oat/dex2oat.ccstatic dex2oat::ReturnCode Dex2oat(int argc, char** argv) {**************************************dex2oat::ReturnCode setup_code = dex2oat->Setup();dex2oat::ReturnCode result;if (dex2oat->IsImage()) {result = CompileImage(*dex2oat);} else {result = CompileApp(*dex2oat);}**************************************}

3.类加载流程
1.隐式加载:(1)创建类的实例,也就是new一个对象(2)访问某个类或接口的静态变量,或者对该静态变量赋值(3)调用类的静态方法(4)反射Class.forName("android.app.ActivityThread")(5)初始化一个类的子类(会首先初始化子类的父类)2.显示加载:(1)使用LoadClass()加载(2)使用forName()加载
Class.forName 和 ClassLoader.loadClass加载有何不同:(1)ClassLoader.loadClass也能加载一个类,但是不会触发类的初始化(也就是说不会对类的静态变量,静态代码块进行初始化操作)(2)Class.forName这种方式,不但会加载一个类,还会触发类的初始化阶段,也能够为这个类的静态变量,静态代码块进行初始化操作



ConvertJavaArrayToDexFiles对cookie进行了处理
art/runtime/class_linker.ccmirror::Class* ClassLinker::DefineClass(Thread* self,const char* descriptor,size_t hash,Handle<mirror::ClassLoader> class_loader,const DexFile& dex_file,const DexFile::ClassDef& dex_class_def) {***************LoadClass(self, *new_dex_file, *new_class_def, klass);***************}
art/runtime/class_linker.ccvoid ClassLinker::LoadClass(Thread* self,3120 const DexFile& dex_file,3121 const DexFile::ClassDef& dex_class_def,3122 Handle<mirror::Class> klass) {3123 const uint8_t* class_data = dex_file.GetClassData(dex_class_def);3124 if (class_data == nullptr) {3125 return; // no fields or methods - for example a marker interface3126 }3127 LoadClassMembers(self, dex_file, class_data, klass);3128}
art/runtime/class_linker.ccvoid ClassLinker::LoadClassMembers(Thread* self,const DexFile& dex_file,const uint8_t* class_data,Handle<mirror::Class> klass) {***************LoadMethod(dex_file, it, klass, method);LinkCode(this, method, oat_class_ptr, class_def_method_index);***************}
art/runtime/class_linker.ccvoid ClassLinker::LoadMethod(const DexFile& dex_file,const ClassDataItemIterator& it,Handle<mirror::Class> klass,ArtMethod* dst) {}

art/runtime/interpreter/interpreter.ccstatic inline JValue Execute(Thread* self,const DexFile::CodeItem* code_item,ShadowFrame& shadow_frame,JValue result_register,bool stay_in_interpreter = false) REQUIRES_SHARED(Locks::mutator_lock_){***************ArtMethod *method = shadow_frame.GetMethod();***************}
4.DexFile详解



(1)直接查找法


(2)间接查找法
getDexFile()getMethod()
5.ArtMethod详解

五
脱壳技术归纳

1.现有工具脱壳法

(1)FRIDA-DEXDump


(2)FDex2
(3)其他工具

2.Hook脱壳法








3.插桩脱壳法

//addchar dexfilepath[100]=0;memset(dexfilepath,0,100);sprintf(dexfilepath,"%d_%zu_LoadMethod.dex",getpid(),dex_file.Size());int dexfd = open(dexfilepathm,O_CREAT|O_RDWR,666);if(dexfd>0){int result = write(dexfd,dex_file.Begin(),dex_file.Size());if(result>0){close(dexfd);LOG(WARNING)<<"LoadMethod"<<dexfilepath;}}//add



4.反射脱壳法
核心思路:反射 + mCookie步骤:1、找到加固apk的任一class,一般选择主Application或Activity2、通过该类找到对应的Classloader3、通过该Classloader找到BaseDexClassLoader4、通过BaseDexClassLoader找到其字段DexPathList5、通过DexPathList找到其变量Element数组dexElements6、迭代该数组,该数组内部包含DexFile结构7、通过DexFile获取其变量mCookie和mFileName至此我们已经获取了mCookie对该mCookie的解释:#1、4.4以下好像,mCookie对应的是一个int值,该值是指向native层内存中的dexfile的指针#2、5.0是一个long值,该值指向native层std::vector<const DexFile*>* 指针,注意这里有多个dex,你需要找到你要的#3、8.0,该值也是一个long型的值,指向底层vector,但是vector下标0是oat文件,从1开始是dex文件// 至于你手机是那个版本,如果没有落入我上面描述的,你需要自己看看代码8、根据mCookie对应的值做转换,最终你能找到dexfile内存指针9、把该指针转换为dexfile结构,通过findClassDef来匹配你所寻找的dex是你要的dex10、dump写文件




5.动态调试脱壳法







static main(void){auto fp, begin, end, dexbyte;fp = fopen("d:\\dump.dex", "wb+");begin = 0x76FCD93020;end = begin + 0x7EEC5600;for ( dexbyte = begin; dexbyte<end;dexbyte++){fputc(Byte(dexbyte), fp);}}




6.特殊API脱壳法










六
实验总结
参考文献
https://bbs.pediy.com/thread-252630.htm#msg_header_h2_4
https://bbs.pediy.com/thread-254555.htm#msg_header_h2_4
https://www.anquanke.com/post/id/221905?display=mobile
https://www.qj301.com/news/317.html

看雪ID:随风而行aa
https://bbs.pediy.com/user-home-905443.htm
# 往期推荐


球分享

球点赞

球在看

点击“阅读原文”,了解更多!
[广告]赞助链接:
关注数据与安全,洞悉企业级服务市场:https://www.ijiandao.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
关注KnowSafe微信公众号随时掌握互联网精彩
- WeWe RSS搭建公众号文章自动采集管理器
- Laravel框架惊现高危漏洞 攻击者可肆意植入恶意脚本
- 免费SSL证书与付费SSL证书有啥不一样?
- logocreator快速创建专业logo工具
- Nginx服务器安装SSL证书
- 人物 | 悬镜安全random:解密供应链安全情报的头号黑客
- UBBF 2022|一起登陆超宽带5.5G数字之城
- 骁龙奇遇派对|与大咖漫游七大星球的最后机会
- 高通宣布利用5G毫米波和Sub-6GHz聚合成功完成数据呼叫
- 网安好播客 | 想要好成绩?第一届冠亚季军来支招了
- 亚洲诚信联合263企业邮箱 推出数字签名解决方案
- 亚洲诚信揽获DigiCert亚太区圆桌会议两项重磅大奖




