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 ...
随机推荐
- [转帖]tidb4.0.4使用tiup扩容TiKV 节点
https://blog.csdn.net/mchdba/article/details/108896766 环境:centos7.tidb4.0.4.tiup-v1.0.8 添加两个tikv节点 ...
- [转帖]nginx上传模块—nginx upload module-
https://www.cnblogs.com/lidabo/p/4171515.html 一. nginx upload module原理 官方文档: http://www.grid.net.ru/ ...
- 【转贴】2019.3 学习向SP打造指南 篇一:微软神器Surface产品线全系列详细介绍
学习向SP打造指南 篇一:微软神器Surface产品线全系列详细介绍 2019-03-01 22:30:00 161点赞 699收藏 141评论 https://post.smzdm.com/p/a5 ...
- Mark 一下 Redisson 可能需要升级版本
貌似有bug 我们高并发的情况下貌似遇到了 https://github.com/redisson/redisson/issues/2299
- 分布式事务和Spanner分布式数据库
一.分布式事务 首先事务可以这么理解:程序员有一些不同的操作,或许针对数据库不同的记录,他们希望所有这些操作作为一个整体,不会因为失败而被分割,也不会被其他活动看到中间状态.事务处理系统要求程序员对这 ...
- 【发现一个问题】macos m2 下无法使用 x86_64-linux-musl-gcc 链接含有 avx512 指令的 c 代码
作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 一开始是使用 golang 中的 cgo 来编译: env ...
- 【JS 逆向百例】网洛者反爬练习平台第七题:JSVMPZL 初体验
关注微信公众号:K哥爬虫,持续分享爬虫进阶.JS/安卓逆向等技术干货! 声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后 ...
- 【JS 逆向百例】网洛者反爬练习平台第三题:AAEncode 加密
关注微信公众号:K哥爬虫,持续分享爬虫进阶.JS/安卓逆向等技术干货! 声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后 ...
- 深入浅出Java多线程(一):进程与线程
引言 大家好,我是你们的老伙计秀才. 在计算机系统的发展历程中,早期的计算机操作模式十分单一和低效.用户只能逐条输入指令,而计算机则按照接收指令的顺序逐一执行,一旦用户停止输入或进行思考,计算机会处于 ...
- LyScript 验证PE程序开启的保护
有些漏洞利用代码需要在某个保护模式被关闭的情况下才可以利用成功,在此之前需要得到程序开启了何种保护方式.验证其实有很多方法,其原理是读入PE文件头部结构,找到OPTIONAL_HEADER.DllCh ...