dalvik下替换so简单dump出梆梆加固保护的odex

标签: c++ android 脱壳 梆梆 dalvik
15634人阅读 评论(0) 收藏 举报
分类:
  • 由于保护技术更迭迅速,不保证本文方法适用于后续或者其它版本的梆梆加固,需要读者自行测试。

        梆梆加固后的apk,里面的classes.dex只是个外壳,负责加载libDexHelper.so,而真正的dex被加密放在了\assets\classes0.jar,这不是常规的jar文件无法直接解压,我们的目标就是从内存中dump出解密后的classes0.jar/.dex(并进行适当修复)。

        内存dump的方案有很多, 比如直接dd、DexHunter等,但都有一些缺点,dd需要内存地址和大小,这就要求进行暴力搜索,但是内存完全可能被处理过,dump时机很难掌握,于是转到动态调试期望在关键点下断,但由于反调试对抗的存在又会有一堆麻烦;DexHunter很强大,但它要求定制系统,这对逆向工程来说当然不是事,只是同样有针对DexHunter类似原理的对抗,比如检测不安全ROM,等等这些都会带来不必要麻烦,本文提供的方法似乎直接跳过了这些坑。

        其实,无论是DexHunter还是本文提供的方案,最关键的不过是取得代码执行先机,因为代码一但被安全的注入,在壳启动前执行我们的代码,我们就能完成很多有意思的事情了。怎么实现呢?我们知道libDexHelper.so是整个壳的核心,修改libDexHelper.so?万一有自校验咋办,这里就有了两种替换方案,第一种查看依赖较少的so,将该依赖的so换成我们的,比如liblog.so,但是这要求我们实现和liblog.so相应的接口,否则dlopen会失败,第二种,也是本文采用的方案,我们直接把它替换了不就好了嘛?

        首先,把原来的libDexHelper.so改成libDexHelper2.so, 然后我们需要写一个libDexHelper.so,只需实现JNI_OnLoad即可, 在里面加载libDexHelper2.so并显式调用JNI_OnLoad即可(这里竟然没有检测文件名,呵呵,不过检测也没多大意义,容易bypass。另外由于我用的x86机,梆梆在.cache目录下生成对应的x86 so才是目标):

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *unused)
{
	#define SO "/data/data/blog.csdn.net.rrrfff/.cache/libDexHelper-x862.so"
	LOGI("Trying to load lib " SO " 0x0");
	void *hk = dlopen(SO, RTLD_GLOBAL | RTLD_NOW);
	LOGI("Added shared lib " SO " %p %s", hk, hk == NULL ? dlerror() : "");

	void *ld = dlsym(hk, __FUNCTION__);
	LOGI("JNI_OnLoad at %p", ld);
	reinterpret_cast<__func_type(JNI_OnLoad)>(ld)(jvm, unused);
}
        adb push我们的so到相应目录,am start启动目标app,运行一切正常,代码被顺利注入,接着我们切入重点,把dex相关的api统统hook一遍看看是咋回事,这里我把dexFile*全家桶都给hook了,包括dvmDexFileOpenFromFd,dvmDexFileFree,dexFileParse,dvmDexChangeDex1,dvmDexChangeDex2。
        通过被hook方法的执行情况,我们大概总结下梆梆加固还原dex的大概流程(dalvik系统):
  1. open读取 \assets\classes0.jar 并解密出真正的jar,然后解压到.cache目录,但此时的classes0.dex仍旧是加密的,这步仅.cache目录下没有对应.dex时会发生。
  2. open读取 classes0.dex 并解密出真正的dex,其实是odex,看文件头可以知道。
  3. 通过dvmDexFileOpenFromFd读取dex文件,这里的fd就是上一步的fd,进行内存映射。
  4. 系统调用dexFileParse对映射好的dex内存进行解析并返回DexFile结构,这里是dump的好时机。
  5. 壳调用dvmDexChangeDex2对已解析好的dex内存区进行混淆,写入脏数据,所以我们在程序跑起来后再dump出来的东西是有噪声的。

        知道了这些之后我们就能愉快的干活了,编写代码在dexFileParse时完成dump(据说梆梆把一堆read,write,mmap等libc函数hook了, 防止读取相关dex的区域, 这里绕了个弯,先用一般人不会用到的memmove备份内存,然后跳过dex头写到文件,其实似乎多此一举):

	static info<DvmDex *(*)(const uint8_t* data, size_t length, int flags)> *p0 = NULL;
	p0 = p0->hook(dlsym(dvm, "_Z12dexFileParsePKhji"),
				  [](const uint8_t* data, size_t length, int flags)->DvmDex * {
		LOGI("dexFileParse hit with %p %u %d", data, length, flags);
		if (length > 100000) {
			int  dexv = ::open("/data/data/blog.csdn.net.rrrfff/cache/core.odex", O_CREAT | O_TRUNC | O_WRONLY);
			auto dexp = reinterpret_cast<uint8_t *>(::malloc(length));
			::memmove(dexp, data, length);
			::write(dexv, "bmp", 3);
			::write(dexv, dexp + 3, dexl - 3); // bypass odex header
			::free(dexp);
			::close(dexv);
		} //if
		auto pDvmDex = p4->invoke()(data, length, flags);
		return pDvmDex;
	});

        顺利得到core.odex,jeb可以正常打开,但是会出现一些异常,估计存在一些反反编译手段,我们用DexClassLoader加载试试(因为我们加载的是odex, 所以需要在同一目录下放一个dex, 这里我随便弄了个zip包, 系统发现里面没dex会加载同目录下的odex):

	DexClassLoader *dexloader = new DexClassLoader(env,
												   "/data/data/blog.csdn.net.rrrfff/cache/core.dex",
												   "/data/data/blog.csdn.net.rrrfff/.cache",
												   "/tmp",
												   DexClassLoader::getSystemLoader(env));
	auto cls  = dexloader->loadClass(env, "blog.csdn.net.rrrfff.TBJWelcomeActivity");
	auto cls2 = dexloader->loadClass(env, "blog.csdn.net.rrrfff.bean.UserInfoModel");
	LOGI("cls = %p %p", cls, cls2);
        类加载成功, 证明dump出的odex格式良好(对ClassLoader而言), 为了试试能否正常运行, 这里懒得去进行重打包、签名这些琐碎的工作,我们直接hook dvmLookupClass让它返回找不到的类(去掉对原SO的加载后会报类找不到):

	static info<void *(*)(const char *descriptor, void *loader, bool unprepOkay)> *p0 = NULL;
	p0 = p0->hook(dlsym(dvm, "_Z14dvmLookupClassPKcP6Objectb"),
				  [](const char *descriptor, void *loader, bool unprepOkay)->void * {
		LOGI("dvmLookupClass hit with %s %p %d", descriptor, loader, unprepOkay);
		static volatile int during_load[10240] = { 0 };
		if (during_load[gettid()] == 0 && strstr(descriptor, "Lblog/csdn/net/rrrfff") == descriptor ) {
			char sdescriptor[128];
			int len = strlen(descriptor);
			::memcpy(sdescriptor, descriptor + 1, len - 2);
			sdescriptor[len -= 2] = 0;
			while (--len >= 0) {
				if (sdescriptor[len] == '/') sdescriptor[len] = '.';
			}
			JNIEnv *env;
			g_jvm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
			LOGI("loading %s", sdescriptor);
			during_load[gettid()] = 1;
			auto cls = dexloader->loadClass(env, sdescriptor);
			during_load[gettid()] = 0;
			LOGI("%s loaded at %p", sdescriptor, cls);
			return decode_obj(get_self(), cls);
		} //if
		void *ret = p0->invoke()(descriptor, loader, unprepOkay);
		return ret;
	});
        看日志可以发现很多类被成功加载了,但这里会报一个native方法找不到, com/secneo/apkwrapper/Helper的attach方法, 签名(Landroid/app/Application;Landroid/content/Context;)V, 看参数有Application和Context,应该是壳完成资源修复等工作的切入点,后续再研究,我们给它注册个:

	void(*attach)(JNIEnv *env, jclass, jobject, jobject) = [](JNIEnv *env, jclass, jobject, jobject) {
		DEBUG_LINE_HIT;
	};
	JNINativeMethod gMethods[] = {
		{ "attach", "(Landroid/app/Application;Landroid/content/Context;)V", reinterpret_cast<void *>(attach) }
	};
	env->RegisterNatives(env->FindClass("com/secneo/apkwrapper/Helper"), gMethods, 1);
        最后在 android.content.ContextWrapper.getResources(ContextWrapper.java:89) 处出现 java.lang.NullPointerException 异常, 可能是资源或者那个地方需要修复, 这里就没去深究了, 因为从jeb我们已经得到了想要的结果。

查看评论

Android常见App加固厂商脱壳方法的整理

目录 简述(脱壳前学习的知识、壳的历史、脱壳方法)第一代壳第二代壳第三代壳第N代壳 简述 Apk文件结构Dex文件结构壳史壳的识别 Apk文件结构 Dex文件结构...
  • QQ1084283172
  • QQ1084283172
  • 2016年12月11日 12:29
  • 7487

Android常见App加固厂商脱壳方法的整理

目录 简述(脱壳前学习的知识、壳的历史、脱壳方法) 第一代壳 第二代壳 第三代壳 第N代壳 简述 Apk文件结构 Dex文件结构 壳史 壳的识别 Apk文件结构 Dex文件结构 ...
  • mergerly
  • mergerly
  • 2017年03月14日 16:50
  • 5525

BufferedReader

BufferedReader 从字符输入流中读取文本,缓冲各个字符,从而提供字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。 通常,Reader...
  • nightelvesimba
  • nightelvesimba
  • 2010年12月01日 20:21
  • 273

DexHunter学习笔记记录

源地址 http://ju.outofmemory.cn/entry/267514 0x00 前言 前段时间遇到一个app,解包后发现了libDexHekper.so,搜索...
  • playboyma
  • playboyma
  • 2016年07月29日 10:04
  • 1789

12306 2.2版本SO的分析和修复

老早写的,现在已经2.3版本了,把这个放出来,这个方法要比之前的简单很多。       12306的so加载顺序是先libDexHelper.so后libcheckcode.so       一、修复...
  • justFWD
  • justFWD
  • 2016年03月15日 20:09
  • 4086

12306之梆梆加固libsecexe.so的脱壳及修复

12306使用提梆梆企业VIP版本的加固,跟其它梆梆加固一样,都会用到libsecexe.so来做dex的加载,本文对此so做一个脱壳的分析 (一)、so脱壳 此so的最先执行点在init_proc ...
  • justFWD
  • justFWD
  • 2015年12月04日 16:35
  • 5921

Android常见App加固厂商脱壳方法的整理

目录 简述(脱壳前学习的知识、壳的历史、脱壳方法) 第一代壳 第二代壳 第三代壳 第N代壳 简述 Apk文件结构 Dex文件结构 壳史 壳的识别 Apk文...
  • qing666888
  • qing666888
  • 2017年05月07日 00:53
  • 2189

交通银行闪退问题

解决了艺龙闪退的问题后,认为解决交通银行闪退会是同类问题,可是我失望了,没有任何日志输出。 只能反编译看了,返现smali文件夹很小,全部的文件只有如下。 /home/lgy/decode/com.b...
  • firedancer0089
  • firedancer0089
  • 2017年06月19日 13:44
  • 1181

cocos2dx项目移植android平台使用第三方so库被删掉的问题

cocos2dx项目android平台在编译的时候会把libs/armeabi目录清空,这就是为什么我们编译的时候导入的第三方so库会被删掉的原因。 解决方案: 1、 在jni目录下新建prebu...
  • zuihoudeliulang
  • zuihoudeliulang
  • 2015年06月04日 10:51
  • 1456

Android中apk加固完善篇之内存加载dex方案实现原理(不落地方式加载)

时隔半年,困扰的问题始终是需要解决的,之前也算是没时间弄,今天因为有人在此提起这个问题,那么就不能不解决了,这里写一篇文章记录一下吧。那么是什么问题呢?就是关于之前的一个话题:Android中apk加...
  • jiangwei0910410003
  • jiangwei0910410003
  • 2016年06月02日 08:22
  • 17128
    最新评论