开源项目位置(为大佬开源精神点赞)
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);
}