开源项目位置(为大佬开源精神点赞)

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 抽取壳实现原理分析(执行逻辑)的更多相关文章

  1. 老李推荐:第5章7节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles

    老李推荐:第5章7节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles   poptest是国内唯一一家培养测试开 ...

  2. ConcurrentHashMap原理分析(1.7与1.8)-put和 get 需要执行两次Hash

    ConcurrentHashMap 与HashMap和Hashtable 最大的不同在于:put和 get 两次Hash到达指定的HashEntry,第一次hash到达Segment,第二次到达Seg ...

  3. Android函数抽取壳的实现

    0x0 前言 函数抽取壳这个词不知道从哪起源的,但我理解的函数抽取壳是那种将dex文件中的函数代码给nop,然后在运行时再把字节码给填回dex的这么一种壳. 函数抽取前: 函数抽取后: 很早之前就想写 ...

  4. (转)Android 系统 root 破解原理分析

    现在Android系统的root破解基本上成为大家的必备技能!网上也有很多中一键破解的软件,使root破解越来越容易.但是你思考过root破解的 原理吗?root破解的本质是什么呢?难道是利用了Lin ...

  5. Android平台APK分析工具包androguard的部署使用和原理分析

    原创文章,转载请注明出处,谢谢. Android应用程序分析主要有静态分析和动态分析两种,常见的静态分析工具是Apktool.dex2jar以及jdgui.今天突然主要到Google code上有个叫 ...

  6. 老李推荐:第6章8节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-小结

    老李推荐:第6章8节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-小结   本章我们重点围绕处理网络过来的命令的MonkeySourceNetwork这个事 ...

  7. 老李推荐:第5章3节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 启动脚本

    老李推荐:第5章3节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 启动脚本   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性 ...

  8. 老李推荐:第5章2节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 启动流程概览

    老李推荐:第5章2节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 启动流程概览   每个应用都会有一个入口方法来供操作系统调用执行,Monkey这个应用的入口方法就 ...

  9. 老李推荐:第5章1节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 官方简介

    老李推荐:第5章1节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 官方简介   在MonkeyRunner的框架中,Monkey是作为一个服务来接受来自Monkey ...

  10. Hadoop(十四)MapReduce原理分析

    前言 上一篇我们分析了一个MapReduce在执行中的一些细节问题,这一篇分享的是MapReduce并行处理的基本过程和原理. Mapreduce是一个分布式运算程序的编程框架,是用户开发“基于had ...

随机推荐

  1. [转帖]linux中Shell日期转为时间戳的方法

    http://www.nndssk.com/xtwt/169617hFPRvq.html shell中获取时间戳的方式为:date -d "$currentTime" +%s $ ...

  2. 【转帖】一篇文章让你了解灾备指标:RPO与RTO

    RTO 和 RPO 都是企业灾难恢复(Disaster Recovery, DR)需要考虑的关键指标,这两个指标可以用来指导企业来制定合适的业务系统服务或数据的恢复方案. RPO(Recovery P ...

  3. isolcpus的学习与了解

    isolcpus的学习与了解 前言 最近一直跟同事说要进行CPU的bind bind到具体的core 当时还一直装B, 说这样能够提高性能. 但是今天起床早上查看资料时发现,其实是先设置隔离的. 让操 ...

  4. [转帖]适用于 Azure VM 的 TCP/IP 性能优化

    https://learn.microsoft.com/zh-cn/azure/virtual-network/virtual-network-tcpip-performance-tuning?con ...

  5. [转帖]SPECjvm测试工具详解

    ARM服务器测试大纲中指定了要使用specjvm测试Java虚拟机性能,所以就上网找开源的测试套. 简介 SPECjvm2008(java虚拟机基准测试)是用来测试java运行环境(JRE)性能的基准 ...

  6. DashBoard in k8s 简单使用

    DashBoard in k8s 简单使用 第一部分 拉取分发镜像 没办法的事情,公司网络实在是太垃圾了, dockerhub 又不让多次docker pull 找一台网络表好的机器 执行如下命令: ...

  7. 基于Apache PDFBox的PDF数字签名

    在Java语言环境中完成数字签名主要基于itext-pdf.PDFBox两种工具,itext-pdf受商业限制,应用于商业服务中需要购买授权.PDFBox是apache基金会开源项目,基于apache ...

  8. 什么是根号?什么是 log ?

    生日悖论是 \(O(\sqrt{n})\) 随机序列 LIS 是 \(O(\sqrt{n})\) 随机 \(\pm1\) 序列前缀和最大绝对值是 \(O(\sqrt{n})\) 证明 随机 Prufe ...

  9. 特定领域知识图谱融合方案:文本匹配算法之预训练Simbert、ERNIE-Gram单塔模型等诸多模型【三】

    特定领域知识图谱融合方案:文本匹配算法之预训练模型SimBert.ERNIE-Gram 文本匹配任务在自然语言处理中是非常重要的基础任务之一,一般研究两段文本之间的关系.有很多应用场景:如信息检索.问 ...

  10. x64dbg 2022 最新版编译方法

    x64dbg 调试器的源码编译很麻烦,网络上的编译方法均为老版本,对于新版本来说编译过程中会出现各种错误,编译的坑可以说是一个接着一个,本人通过研究总结出了一套编译拳法可以完美编译输出,不过话说回来x ...