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.拿到LoadedApk
ArrayMap mPackagesObj = (ArrayMap) mPackagesField.get(activityThreadObj);
String packagename = context.getPackageName();
WeakReference wr = (WeakReference) mPackagesObj.get(packagename);
Object LoadApkObj = wr.get();
//4.拿到mclassLoader
Class 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父节点设置为DexClassLoader
ClassLoader 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.java
public 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.java
DexFile(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.cc
std::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.cc
bool 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.cc
int 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.cc
static 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.cc
mirror::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.cc
void 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 interface
3126 }
3127 LoadClassMembers(self, dex_file, class_data, klass);
3128}
art/runtime/class_linker.cc
void 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.cc
void ClassLinker::LoadMethod(const DexFile& dex_file,
const ClassDataItemIterator& it,
Handle<mirror::Class> klass,
ArtMethod* dst) {
}
art/runtime/interpreter/interpreter.cc
static 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.插桩脱壳法
//add
char 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或Activity
2、通过该类找到对应的Classloader
3、通过该Classloader找到BaseDexClassLoader
4、通过BaseDexClassLoader找到其字段DexPathList
5、通过DexPathList找到其变量Element数组dexElements
6、迭代该数组,该数组内部包含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是你要的dex
10、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/
随时掌握互联网精彩
- 鸿蒙智行成立一年累计交付破50万辆:余承东在线致谢
- Lemon8再次APP Store霸榜,或作为TikTok在美国的替代方案
- 喜报,又拍云入选“2022 年度优秀开源技术团队”
- 一文读懂 Kubernetes 存储设计
- RotaJakiro后门与Buni后门关联性分析
- 腾讯陈东东:Caelus全场景在离线混部的思考与实践
- 网站被入侵、攻击,相关企业要负责任吗?
- 一年5个孵化项目来自于中国,佛系Apache如何帮助中国开源立足全球?
- 做技术开发到老 or 晋升管理层,程序员的终极目标是什么?
- 华为与传化集团开展全面战略合作,共同推动产业数字化转型
- 什么是EV SSL证书?
- 安卓模拟器 (NoxPlayer) 被用于供应链攻击