标题要牛逼点,才有人看吧!
apk加载方案相关文章不少,但是要么是一代(不落地)加载方案,要么二代方案做的不完善,Android高版本支持不了,或者不支持多dex等,反正就是存在各种小问题,离可以使用还有这一点点距离。按理说现在加固方案都到第四代了,没道理二代的完善方案还没人开源吧,仔细想想可能是现在方案更先进了,当年的方案问题没人关注了,二来加固工具使用起来也方便,没必要自己搞一个。
为了让后来人有个学习参考吧,这里把自己鼓捣的结果写出来,大家共同学习共同进步吧!
------------------------------------ 废话分割线 -------------------------------------------
8.0及以后有系统提供的内存加载dex接口,所以难点主要集中在8.0以前。
8.0以前实现原理是加载虚dex然后替换成真实的cookie返回。这里如何替换是个难点,好在网上大神已经开源了方案,只是一番尝试下来只能在5.0和5.1上使用,并且也不支持多dex。网上查找相关资料,没有找到有用的结果,可能是搜索姿势不对。
那么就只能自己来了,首先查看6.0及以上部分的源码,需要hook的函数OpenMemory与5.1相比,返回发生了变化
std::unique_ptr<const DexFile> DexFile::OpenMemory(const std::string& location,
uint32_t location_checksum,
MemMap* mem_map,
std::string* error_msg)
这个好处理,我改改不就行了吗,修改后在6.0上ok了,难道就这么搞定了。拿着代码去7.1上跑跑,报dex的错误。
问题一:
A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
A/DEBUG: Build fingerprint: 'qcom/msm8909/msm8909:7.1.2/N2G47H/54:user/release-keys'
A/DEBUG: Revision: '0'
A/DEBUG: ABI: 'arm'
A/DEBUG: pid: 13210, tid: 13210, name: m.frezrik.jiagu >>> com.frezrik.jiagu <<<
A/DEBUG: signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
A/DEBUG: Abort message: 'art/runtime/class_linker.cc:3225] Check failed: dex_cache_length > 0u (dex_cache_length=0, 0u=0) '
A/DEBUG: r0 00000000 r1 0000339a r2 00000006 r3 00000008
A/DEBUG: r4 ae5ca584 r5 00000006 r6 ae5ca52c r7 0000010c
A/DEBUG: r8 ab02f3df r9 9f3e63c0 sl 0000000a fp ab084400
A/DEBUG: ip 00000000 sp be61e3d8 lr ab5215a7 pc ab523e28 cpsr 200f0010
A/DEBUG: backtrace:
A/DEBUG: #00 pc 00049e28 /system/lib/libc.so (tgkill+12)
A/DEBUG: #01 pc 000475a3 /system/lib/libc.so (pthread_kill+34)
A/DEBUG: #02 pc 0001d7f5 /system/lib/libc.so (raise+10)
A/DEBUG: #03 pc 00019331 /system/lib/libc.so (__libc_android_abort+34)
A/DEBUG: #04 pc 000172e8 /system/lib/libc.so (abort+4)
A/DEBUG: #05 pc 000b7f89 /system/lib/libart.so (offset 0x258000)
一番源码看下来,发现在7.0上加了dex check的代码,难道在7.0上真的没办法了,不死心的我突然想到8.0不是已经实现了内存加载dex吗,看看他们怎么做的呗。阅读了相关代码后,也没发现什么特殊的地方,能算上可疑点的是给内存的dex location命名(只记得这个解决问题的可疑点了,笑)
static const DexFile* CreateDexFile(JNIEnv* env, std::unique_ptr<MemMap> dex_mem_map) {
std::string location = StringPrintf("Anonymous-DexFile@%p-%p",
dex_mem_map->Begin(),
dex_mem_map->End());
std::string error_message;
std::unique_ptr<const DexFile> dex_file(DexFile::Open(location,
0,
std::move(dex_mem_map),
/* verify */ true,
/* verify_location */ true,
&error_message));
死马当活马医,先加了试试
std::unique_ptr<const void *> load23(void *artHandle, const char *base, size_t size) {
std::string location = "Anonymous-DexFile"; //解决问题的关键就是这里
std::string err_msg;
std::unique_ptr<const void *> value;
const auto *dex_header = reinterpret_cast<const Header *>(base);
auto func23 = (org_artDexFileOpenMemory23) ndk_dlsym(artHandle, OpenMemory23);
value = func23((const unsigned char *) base,
size,
location,
dex_header->checksum_,
nullptr,
nullptr,
&err_msg);
if (!value) {
LOGE("[-]call load23 failed");
return nullptr;
}
return value;
}
跑一把,成功了,此时的我流下了激动的泪水。
2022-10-29补充:
问题二:
近来github上有几位小伙伴发现在Android10上有问题
错误信息如下:
java.lang.InternalError: Attempt to register dex file /data/user/0/xx.xx.xx/Anonymous-DexFile@1784353095.jar with multiple class loaders
at dalvik.system.DexFile.defineClassNative(Native Method)
at dalvik.system.DexFile.defineClass(DexFile.java:292)
at dalvik.system.DexFile.loadClassBinaryName(DexFile.java:285)
at dalvik.system.DexPathList$Element.findClass(DexPathList.java:778)
at dalvik.system.DexPathList.findClass(DexPathList.java:538)
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:194)
at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
at com.frezrik.jiagu.StubApp.invoke2(StubApp.java:12)
at com.frezrik.jiagu.StubApp.attach(Native Method)
at com.frezrik.jiagu.StubApp.attachBaseContext(StubApp.java:66)
我自己编译的demo验证是没问题,然后就找小伙伴要来了出问题的app(感谢小伙伴的支持)。然后着手解决,但是近来因为在评审一个小伙伴代码时的疏忽导致已发布的固件出现bug,这个事弄得我极为郁闷,就一直没时间去处理这个问题,还好今天准备发新版本了。言归正传,分析下这个问题。
首先在android10源码找到这段日志输出的位置:art/runtime/class_linker.cc
ObjPtr<mirror::DexCache> ClassLinker::EnsureSameClassLoader(
Thread* self,
ObjPtr<mirror::DexCache> dex_cache,
const DexCacheData& data,
ObjPtr<mirror::ClassLoader> class_loader) {
DCHECK_EQ(dex_cache->GetDexFile(), data.dex_file);
if (data.class_table != ClassTableForClassLoader(class_loader)) {
self->ThrowNewExceptionF("Ljava/lang/InternalError;",
"Attempt to register dex file %s with multiple class loaders",
data.dex_file->GetLocation().c_str());
return nullptr;
}
return dex_cache;
}
对比了下8.0的代码,这部分逻辑都一样。既然这里看不出问题,那就现象我们是怎么把dex加载到内存的,顺着系统提供的内存加载dex接口InMemoryDexClassLoader看看。
可以发现在8.0的libcore/dalvik/src/main/java/dalvik/system/DexPathList.java构造方法里调用了makeInMemoryDexElements方法创建dexElements。
而android10上是在libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java调用
pathList.initByteBufferDexPath,在创建DexFile对象时,指定classLoader是definingContext,就是它导致的问题!!!还好在android10的源码里仍然保留了makeInMemoryDexElements,可以看到它指定的classLoader是null。
// This method is not used anymore. Kept around only because there are many legacy users of it.
@SuppressWarnings("unused")
@UnsupportedAppUsage
public static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles,
List<IOException> suppressedExceptions) {
Element[] elements = new Element[dexFiles.length];
int elementPos = 0;
for (ByteBuffer buf : dexFiles) {
try {
DexFile dex = new DexFile(new ByteBuffer[] { buf }, /* classLoader */ null,
/* dexElements */ null);
elements[elementPos++] = new Element(dex);
} catch (IOException suppressed) {
System.logE("Unable to load dex file: " + buf, suppressed);
suppressedExceptions.add(suppressed);
}
}
if (elementPos != elements.length) {
elements = Arrays.copyOf(elements, elementPos);
}
return elements;
}
看注释都告诉我们有很多legacy users of it,我们主动调用它,覆盖原先的dexElements即可解决该问题。代码如下:
if (g_sdk_int >= 29) {
jclass list_jcs = env->FindClass("java/util/ArrayList");
jmethodID list_init = env->GetMethodID(list_jcs, "<init>", "()V");
jobject list_obj = env->NewObject(list_jcs, list_init);
dexElements = static_cast<jobjectArray>(CallStaticMethod("dalvik/system/DexPathList", "makeInMemoryDexElements", "([Ljava/nio/ByteBuffer;Ljava/util/List;)[Ldalvik/system/DexPathList$Element;", dexBufferArr, list_obj).l);
}
对抗反思:
这种dex文件全部加载到内存的方式,是比较容易被dump的。方案的先天缺陷无法弥补,不过我们可以增加点dump的难度。比如在native层里反调试,对root、Xposed、frida、模拟器等等的检测,这些手段就需要小伙伴自行添加了。
最后上干货: GitHub - Frezrik/Jiagu: Android apk jiaguhttps://github.com/Frezrik/Jiagu
这里也不要积分下载了,大家如果觉得又有,打个星呗!
巨人的肩膀:
https://bbs.pediy.com/thread-225303.htm