dpt-shell 抽取壳实现原理分析(执行逻辑)
开源项目位置(为大佬开源精神点赞)
https://github.com/luoyesiqiu/dpt-shell
抽取壳分为两个步骤
加壳逻辑:
一 对apk进行解析,将codeItem抽出到一个文件中,并进行nop填充
二 对抽取后的apk进行加密
三 注入壳程序相关文件即配置信息
执行逻辑:
一 壳程序执行
二 壳解密抽取后的dex,并完成classloader的替换
三 hook住执行方法,在执行对应函数时进行指令填充,使程序正确执行
执行逻辑
执行逻辑在shell module中,毕竟本身就是先运行壳程序
知识点
ActivityThread.handleBindApplication() 按以下顺序加载 APK 并加载应用程序组件:
加载应用程序 AppComponentFactory 子类并创建一个实例。
调用 AppComponentFactory.instantiateClassLoader()。
调用 AppComponentFactory.instantiateApplication() 来加载应用程序 Application 子类并创建一个实例。
对于每个声明的 ContentProvider,按优先级顺序,调用 AppComponentFactory.instantiateProvider() 来加载它的类并创建一个实例,然后调用 ContentProvider.onCreate()。
调用 Application.attachBaseContext()。
调用 Application.onCreate()。
那么首先执行的是ProxyComponentFactory 的instantiateClassLoader
/**
* This method add in Android 10
*/
@Override
public ClassLoader instantiateClassLoader(ClassLoader cl, ApplicationInfo aInfo) {
Log.d(TAG, "instantiateClassLoader() called with: cl = [" + cl + "], aInfo = [" + aInfo + "]");
ClassLoader classLoader = init(cl);
shellClassLoader = cl;
AppComponentFactory targetAppComponentFactory = getTargetAppComponentFactory(classLoader);
Global.sIsReplacedClassLoader = true;
if(targetAppComponentFactory != null) {
try {
Method method = AppComponentFactory.class.getDeclaredMethod("instantiateClassLoader", ClassLoader.class, ApplicationInfo.class);
return (ClassLoader) method.invoke(targetAppComponentFactory, classLoader, aInfo);
} catch (Exception e) {
}
}
return super.instantiateClassLoader(classLoader, aInfo);
}
private ClassLoader init(ClassLoader cl){
if(!Global.sLoadedDexes){
Global.sLoadedDexes = true;
JniBridge.ia(null,cl);
String apkPath = JniBridge.gap();
String dexPath = JniBridge.gdp();
Log.d(TAG, "init dexPath: " + dexPath + ",apkPath: " + apkPath);
newClassLoader = ShellClassLoader.loadDex(apkPath,dexPath);
Log.d(TAG,"ProxyComponentFactory init() shell classLoader = " + cl);
Log.d(TAG,"ProxyComponentFactory init() app classLoader = " + newClassLoader);
return newClassLoader;
}
Log.d(TAG,"ProxyComponentFactory init() tail shell classLoader = " + cl);
Log.d(TAG,"ProxyComponentFactory init() tail app classLoader = " + newClassLoader);
return newClassLoader;
}
在init中完成了, 真是源dex的加载,和对应的classloader的生成,然后代位执行了原dex的instantiateClassLoader
从Android P开始,Android添加了android.app.AppComponentFactory类,它允许开发者覆盖Android的常用组件。
AppComponentFactory支持开发者对Application,Activity,Service,Receiver,Provider,ClassLoader(AndroidQ支持)等组件的替换。
这意味着开发者想替换Application等组件时不用写一堆反射代码了,对加固或者插件开发者带来极大的便利。
dpt在AppComponentFactory类的instantiateClassLoader和instantiateApplication函数中做了替换ClassLoader和Application的操作。
public Application instantiateApplication(ClassLoader cl, String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Log.d(TAG, "instantiateApplication() called with: cl = [" + cl + "], className = [" + className + "]");
ClassLoader appClassLoader = init(cl);
AppComponentFactory targetAppComponentFactory = null;
String applicationName = JniBridge.rapn(null);
if(!Global.sIsReplacedClassLoader){
JniBridge.mde(cl, appClassLoader);
Global.sIsReplacedClassLoader = true;
shellClassLoader = cl;
targetAppComponentFactory = getTargetAppComponentFactory(cl);
}
else{
targetAppComponentFactory = getTargetAppComponentFactory(appClassLoader);
}
ClassLoader apacheHttpLibLoader = ShellClassLoader.loadDex(Global.APACHE_HTTP_LIB);
JniBridge.mde(cl, apacheHttpLibLoader);
JniBridge.rde(cl, "base.apk");
Global.sNeedCalledApplication = false;
....
}
mde 用于两个classloader 的 dexElement 的合并, rde 用于删除目标element,在这里主要是将源dex的element 放到当前classloader的最前面,并删除base.apk(壳程序的)对应的element
最后调用ProxyApplication 的onCreate (attachBaseContext里没干啥,都在之前干了)
public void onCreate() {
super.onCreate();
Log.d(TAG, "dpt onCreate");
Log.d(TAG, "onCreate() classLoader = " + getApplicationContext().getClassLoader());
String realApplicationName = FileUtils.readAppName(getApplicationContext());
if (Global.sNeedCalledApplication && !TextUtils.isEmpty(realApplicationName)) {
Log.d(TAG, "onCreate: " + realApplicationName);
JniBridge.craa(getApplicationContext(), realApplicationName);
JniBridge.ra(realApplicationName);
JniBridge.craoc(realApplicationName);
Global.sNeedCalledApplication = false;
}
}
craa 对应callRealApplicationAttach
ra 对应replaceApplication
craoc 对应callRealApplicationOnCreate
oCreate里完成了源dex的Application的替换了生命周期函数的调用,开始运行源dex的程序代码
到这里源dex(抽取过后的dex)就跑起来了,但更关键的是何时填充代码呢
2. 填充逻辑
dpt.h文件中
INIT_ARRAY_SECTION void init_dpt();
也就是说该函数被添加至INIT_ARRAY,会在so加载后立即执行
dpt.cpp
void init_dpt() {
DLOGI("init_dpt call!");
dpt_hook();
}
dpt_hook.cpp
void dpt_hook() {
bytehook_init(BYTEHOOK_MODE_AUTOMATIC,false);
g_sdkLevel = android_get_device_api_level();
hook_mmap();
hook_GetOatDexFile();
hook_DefineClass();
}
在这里完成了对mmap的hook 和 DefineClass的hook
PS: 这里利用了Dobby和bhook两个hook框架,暂不清楚只用一个行不行(为啥要用两个呢)
mmap
void* fake_mmap(void* __addr, size_t __size, int __prot, int __flags, int __fd, off_t __offset){
BYTEHOOK_STACK_SCOPE();
int hasRead = (__prot & PROT_READ) == PROT_READ;
int hasWrite = (__prot & PROT_WRITE) == PROT_WRITE;
int prot = __prot;
if(hasRead && !hasWrite) {
prot = prot | PROT_WRITE;
DLOGD("fake_mmap call fd = %p,size = %d, prot = %d,flag = %d",__fd,__size, prot,__flags);
}
if(g_sdkLevel == 30){
char link_path[128] = {0};
snprintf(link_path,sizeof(link_path),"/proc/%d/fd/%d",getpid(),__fd);
char fd_path[256] = {0};
readlink(link_path,fd_path,sizeof(fd_path));
DLOGD("fake_mmap link path = %s",fd_path);
if(strstr(fd_path,"base.vdex") ){
DLOGE("fake_mmap want to mmap base.vdex");
__flags = 0;
}
}
void *addr = BYTEHOOK_CALL_PREV(fake_mmap,__addr, __size, prot, __flags, __fd, __offset);
return addr;
}
void hook_mmap(){
bytehook_stub_t stub = bytehook_hook_single(
getArtLibName(),
"libc.so",
"mmap",
(void*)fake_mmap,
nullptr,
nullptr);
if(stub != nullptr){
DLOGD("mmap hook success!");
}
}
这里时为了加载进来的dex到内存,给这块内存添加写权限,为后面code合并进来做准备
hook_DefineClass
void patchMethod(uint8_t *begin,const char *location,uint32_t dexSize,int dexIndex,uint32_t methodIdx,uint32_t codeOff){
if(codeOff == 0){
DLOGI("[*] patchMethod dex: %d methodIndex: %d no need patch!",dexIndex,methodIdx);
return;
}
auto *dexCodeItem = (dex::CodeItem *) (begin + codeOff);
uint16_t firstDvmCode = *((uint16_t*)dexCodeItem->insns_);
if(firstDvmCode != 0x0012 && firstDvmCode != 0x0016 && firstDvmCode != 0x000e){
NLOG("[*] this method has code no need to patch");
return;
}
auto dexIt = dexMap.find(dexIndex);
if (LIKELY(dexIt != dexMap.end())) {
auto dexMemIt = dexMemMap.find(dexIndex);
//没有放进去过,则放进去
if(UNLIKELY(dexMemIt == dexMemMap.end())){
change_dex_protective(begin,dexSize,dexIndex);
}
auto codeItemMap = dexIt->second;
auto codeItemIt = codeItemMap->find(methodIdx);
if (LIKELY(codeItemIt != codeItemMap->end())) {
data::CodeItem* codeItem = codeItemIt->second;
auto *realCodeItemPtr = (uint8_t *)(dexCodeItem->insns_);
#ifdef NOICE_LOG
char threadName[128] = {0};
getThreadName(threadName);
NLOG("[*] patchMethod codeItem patch ,thread = %s, methodIndex = %d,insnsSize = %d >>> %p(0x%lx)",
threadName,codeItem->getMethodIdx(), codeItem->getInsnsSize(), realCodeItemPtr,(realCodeItemPtr - begin)
);
#endif
memcpy(realCodeItemPtr,codeItem->getInsns(),codeItem->getInsnsSize());
}
else{
DLOGE("[*] patchMethod cannot find methodId: %d in codeitem map, dex index: %d(%s)",methodIdx,dexIndex,location);
}
}
else{
DLOGE("[*] patchMethod cannot find dex: %d in dex map",dexIndex);
}
}
void* DefineClass(void* thiz,void* self,
const char* descriptor,
size_t hash,
void* class_loader,
const void* dex_file,
const void* dex_class_def) {
if(LIKELY(g_originDefineClass != nullptr)){
if(LIKELY(dex_file != nullptr)){
std::string location;
uint8_t *begin = nullptr;
uint64_t dexSize = 0;
int dexIndex = 0;
if(g_sdkLevel >= 28){
auto* dexFileV28 = (V28::DexFile *)dex_file;
location = dexFileV28->location_;
begin = (uint8_t *)dexFileV28->begin_;
dexSize = dexFileV28->size_;
dexIndex = parse_dex_number(&location);
}
else{
auto* dexFileV23 = (V23::DexFile *)dex_file;
location = dexFileV23->location_;
begin = (uint8_t *)dexFileV23->begin_;
dexSize = dexFileV23->size_;
dexIndex = parse_dex_number(&location);
}
if(location.find(DEXES_ZIP_NAME) != std::string::npos){
NLOG("DefineClass location: %s", location.c_str());
if(dex_class_def){
auto* class_def = (dex::ClassDef *)dex_class_def;
NLOG("[+] DefineClass class_idx_ = 0x%x,class data off = 0x%x",class_def->class_idx_,class_def->class_data_off_);
size_t read = 0;
auto *class_data = (uint8_t *)((uint8_t *)begin + class_def->class_data_off_);
uint64_t static_fields_size = 0;
read += DexFileUtils::readUleb128(class_data, &static_fields_size);
NLOG("[-] DefineClass static_fields_size = %lu,read = %zu",static_fields_size,read);
uint64_t instance_fields_size = 0;
read += DexFileUtils::readUleb128(class_data + read, &instance_fields_size);
NLOG("[-] DefineClass instance_fields_size = %lu,read = %zu",instance_fields_size,read);
uint64_t direct_methods_size = 0;
read += DexFileUtils::readUleb128(class_data + read, &direct_methods_size);
NLOG("[-] DefineClass direct_methods_size = %lu,read = %zu",direct_methods_size,read);
uint64_t virtual_methods_size = 0;
read += DexFileUtils::readUleb128(class_data + read, &virtual_methods_size);
NLOG("[-] DefineClass virtual_methods_size = %lu,read = %zu",virtual_methods_size,read);
dex::ClassDataField staticFields[static_fields_size];
read += DexFileUtils::readFields(class_data + read,staticFields,static_fields_size);
dex::ClassDataField instanceFields[instance_fields_size];
read += DexFileUtils::readFields(class_data + read,instanceFields,instance_fields_size);
dex::ClassDataMethod directMethods[direct_methods_size];
read += DexFileUtils::readMethods(class_data + read,directMethods,direct_methods_size);
dex::ClassDataMethod virtualMethods[virtual_methods_size];
read += DexFileUtils::readMethods(class_data + read,virtualMethods,virtual_methods_size);
for(int i = 0;i < direct_methods_size;i++){
auto method = directMethods[i];
NLOG("[-] DefineClass directMethods[%d] methodIndex = %d,code_off = 0x%x",i,method.method_idx_delta_,method.code_off_);
patchMethod(begin, location.c_str(), dexSize, dexIndex, method.method_idx_delta_,method.code_off_);
}
for(int i = 0;i < virtual_methods_size;i++){
auto method = virtualMethods[i];
NLOG("[-] DefineClass virtualMethods[%d] methodIndex = %d,code_off = 0x%x",i,method.method_idx_delta_,method.code_off_);
patchMethod(begin, location.c_str(), dexSize, dexIndex, method.method_idx_delta_,method.code_off_);
}
}
}
}
return g_originDefineClass( thiz,self,descriptor,hash,class_loader, dex_file, dex_class_def);
}
return nullptr;
}
void hook_DefineClass(){
void* defineClassAddress = DobbySymbolResolver(GetClassLinkerDefineClassLibPath(),getClassLinkerDefineClassSymbol());
DobbyHook(defineClassAddress, (void *) DefineClass,(void**)&g_originDefineClass);
}
这里的逻辑时,通过hook DefineClass, 而DefineClass 是执行流程中的必经一环,当执行到这里时,执行patchMethod, 开始对dex对应的位置执行代码填充,最后执行原始的DefineClass,实现正确运行
dpt-shell 抽取壳实现原理分析(执行逻辑)的更多相关文章
- 老李推荐:第5章7节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles
老李推荐:第5章7节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles poptest是国内唯一一家培养测试开 ...
- ConcurrentHashMap原理分析(1.7与1.8)-put和 get 需要执行两次Hash
ConcurrentHashMap 与HashMap和Hashtable 最大的不同在于:put和 get 两次Hash到达指定的HashEntry,第一次hash到达Segment,第二次到达Seg ...
- Android函数抽取壳的实现
0x0 前言 函数抽取壳这个词不知道从哪起源的,但我理解的函数抽取壳是那种将dex文件中的函数代码给nop,然后在运行时再把字节码给填回dex的这么一种壳. 函数抽取前: 函数抽取后: 很早之前就想写 ...
- (转)Android 系统 root 破解原理分析
现在Android系统的root破解基本上成为大家的必备技能!网上也有很多中一键破解的软件,使root破解越来越容易.但是你思考过root破解的 原理吗?root破解的本质是什么呢?难道是利用了Lin ...
- Android平台APK分析工具包androguard的部署使用和原理分析
原创文章,转载请注明出处,谢谢. Android应用程序分析主要有静态分析和动态分析两种,常见的静态分析工具是Apktool.dex2jar以及jdgui.今天突然主要到Google code上有个叫 ...
- 老李推荐:第6章8节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-小结
老李推荐:第6章8节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-小结 本章我们重点围绕处理网络过来的命令的MonkeySourceNetwork这个事 ...
- 老李推荐:第5章3节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 启动脚本
老李推荐:第5章3节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 启动脚本 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性 ...
- 老李推荐:第5章2节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 启动流程概览
老李推荐:第5章2节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 启动流程概览 每个应用都会有一个入口方法来供操作系统调用执行,Monkey这个应用的入口方法就 ...
- 老李推荐:第5章1节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 官方简介
老李推荐:第5章1节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 官方简介 在MonkeyRunner的框架中,Monkey是作为一个服务来接受来自Monkey ...
- Hadoop(十四)MapReduce原理分析
前言 上一篇我们分析了一个MapReduce在执行中的一些细节问题,这一篇分享的是MapReduce并行处理的基本过程和原理. Mapreduce是一个分布式运算程序的编程框架,是用户开发“基于had ...
随机推荐
- [转帖]Welcome to the di-kafkameter wiki!
https://github.com/rollno748/di-kafkameter/wiki#producer-elements Introduction DI-Kafkameter is a JM ...
- [转帖]【dperf系列-5】使用dperf进行性能测试(初级)
https://zhuanlan.zhihu.com/p/451341132 dperf是一款高性能的开源网络压力测试仪,是Linux基金会旗下的DPDK官方生态项目.本文介绍如利用dperf在两台物 ...
- [转帖]自动化回归测试工具 —— AREX 上手实践
https://my.oschina.net/arextest/blog/8589156 AREX 是一款开源的自动化测试工具平台,基于 Java Agent 技术与比对技术,通过流量录制回放能力 ...
- 初识C语言:掌握未来的编程利器
欢迎大家来到贝蒂大讲堂 养成好习惯,先赞后看哦~ 所属专栏:C语言学习 贝蒂的主页:Betty's blog 1. C语言是什么 在我们生活中,我们和父母.朋友.老师交流时候使用的就是 ...
- 震惊p div 标签 可以编辑高度随内容的编辑而发生变化
震惊p标签可以编辑高度随内容的编辑而发生变化### 1==>只可编辑,粘贴复制字段长度不正常 <p contenteditable="true" >这是一个可编辑 ...
- Windows堆管理机制 [1] 堆基础
声明:这篇文章在写的时候,是最开始学习这个堆管理机制,所以写得有些重复和琐碎,基于笔记的目的想写得全一些,这篇文章写的时候参考了很多前辈的文章,已在末尾标出,某些未提及到的可以在评论补充 基于分享的目 ...
- AsNoTracking()非跟踪数据 查询
刚开始学习使用EF ,做项目时需要查询数据将数据显示在datagrid中,使用如下方法: query是IQueryable的 在一次看别人写的代码的时候,发现了AsNoTracking()这个方法,并 ...
- 手撕Vue-Router-实现router-view
前言 在上一篇 [手撕Vue-Router-实现router-link] 中,我们实现了 router-link 组件,这一篇我们来实现 router-view 组件. 实现思路 router-vie ...
- k8s 中的网络
k8s 中的网络模型 CNI 网络插件 CNI 的设计思想 k8s 中的三层网络 Flannel 的 host-gw Calico 参考 k8s 中的网络模型 CNI 网络插件 docker 容器的网 ...
- ARKit的理解与使用
AR概述 AR的意义:让虚拟世界套与现实世界建立联系,并可以进行互动. AR的技术实现:通过实时地计算摄影机输出影像的位置及角度,并在内部通过算法识别将场景中的事物,然后在内部模拟的三维坐标系中给识别 ...