ubuntu-16.04 源码编译 Android-6.0.0_r1 与调试

一次过还是挺开心的,记录下编译过程

环境:ubuntu-16.04 amd64,源码是Android-6.0.0_r1

1、环境配置

1
2
3
4
5
6
7
sudo apt-get install -y git flex bison gperf build-essential libncurses5-dev:i386 \
libx11-dev:i386 libreadline6-dev:i386 libgl1-mesa-dev g++-multilib \
tofrodos python-markdown libxml2-utils xsltproc \
zlib1g-dev:i386 dpkg-dev libsdl1.2-dev libesd0-dev \
git-core gnupg zip curl zlib1g-dev gcc-multilib \
libc6-dev-i386 lib32ncurses5-dev x11proto-core-dev \
libx11-dev lib32z-dev ccache unzip m4

安装 open JDK7

Ubuntu16.04没有open JDK7的源,增加个仓库源

1
2
3
sudo add-apt-repository ppa:openjdk-r/ppa 
sudo apt-get update
sudo apt-get install openjdk-7-jdk

安装后环境变量已经配置好,java -version查看

有些工具需要JDX8才能运行,比如jadx,可以使用下面的命令切换java版本

1
2
sudo update-alternatives --config java
sudo update-alternatives --config javac

2、获得Android源码

使用repo下载文件很大,因为aosp文件夹下的.repo文件是仓库,.repo以外的文件才是需要的源码文件,.repo很大,使用repo下载的源码有几十个G。可以直接下载别人打包好的镜像文件

https://pan.baidu.com/s/1JYOgTrF6xe3cNwmzxmE5Yw

这样android-6.0.0_r1的大小为4G多

谷歌官方repo和教程
https://source.android.com/setup/build/downloading
当然也可以使用清华的repo镜像
https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/

下载下来是7z压缩文件,需要额外的软件解压

1
2
sudo apt-get install p7zip-full
7z x android-6.0.0_r1.7z

解压后修改art/build/Android.common_build.mk,修改内容如下

1
2
3
4
5
6
7
# Host.
ART_HOST_CLANG := false
- ifneq ($(WITHOUT_HOST_CLANG),true)
+ ifeq ($(WITHOUT_HOST_CLANG),false)
# By default, host builds use clang for better warnings.
ART_HOST_CLANG := true
endif

否则会报unsupported reloc…的错误

If it still not works,try this in your android root path:
cp /usr/bin/ld.gold prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6/x86_64-linux/bin/ld

3、编译

在源码根目录下:

1
2
make clobber
source build/envsetup.sh

通过lunch选择编译目标,这里编译aosp_arm-eng,选择1

1
lunch

开始编译

1
make -j8

编译好的文件在out/target/product/generic

启动模拟器

1
emulator -memory 512 -cache-size 2028

若有多个版本

1
2
3
source build/envsetup.sh
lunch
emulator

编译单独的模块

1
mmm art/runtime

重新打包系统镜像

1
make snod

emulator启动参数

-memory size
-cache-size size

https://developer.android.com/studio/run/emulator-commandline?hl=zh-cn

真正使用的命令

1
out/host/linux-x86/bin/emulator -sysdir out/target/product/generic/ -system out/target/product/generic/system.img -ramdisk out/target/product/generic/ramdisk.img -data out/target/product/generic/userdata.img -kernel /home/hzh/oldhome/learn/goldfish/arch/arm/boot/zImage -scale 1.0 -memory 512 -partition-size 1024

关闭ubuntu节能模式

1
sudo pm-powersave false

4、REFERRENCE

https://www.jianshu.com/p/367f0886e62b

http://eternalsakura13.com/2018/02/24/bianyi/


ubuntu16.04 处理wifi问题小记

在实体机上安装ubuntu后,出现了两个问题,一个是ubuntu安装后没有支持笔记本网卡的驱动,通过升级内核解决了;另一个是网卡驱动安装成功后,总是无缘无故掉线,通过关闭wifi的电源管理解决了;期间还卸载了一次双系统中的ubuntu…在此记录一下折腾的内容,方便以后查看。

一、安装网卡驱动

装了ubuntu16.04的双系统,但是没有网卡驱动,网卡型号为realtek 8822be,网上一个简单的方法就是升级linux内核,4.14的内核开始支持该网卡,中间有个些坑,记录一下

下载linux内核文件,手动安装:http://kernel.ubuntu.com/~kernel-ppa/mainline/

但是找不到4.14.0的版本,就下载了4.14.1,以为没有影响,但是安装后,发现无线网还是不能使用,所以通过别人的文件名,拼出来了下载链接,下载了4.14.0的

1
2
3
4
5
http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.14/linux-headers-4.14.0-041400_4.14.0-041400.201711122031_all.deb

http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.14/linux-headers-4.14.0-041400-generic_4.14.0-041400.201711122031_amd64.deb

http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.14/linux-image-4.14.0-041400-generic_4.14.0-041400.201711122031_amd64.deb

安装:

1
sudo dpkg -i *.deb

重启后还是不能使用

然后去/lib/firmware/rtlwifi,发现没有rtl8822be的驱动文件

下载rtl8822be的驱动文件:
https://raw.githubusercontent.com/wkennington/linux-firmware/master/rtlwifi/rtl8822befw.bin

移动到/lib/firmware/rtlwifi目录下,重启后就可以正常使用了

二、关闭wifi的电源管理

wifi时不时会断开,关闭power management解决

一次性手动解决:

1
sudo iwconfig wlp5s0 power off

永久解决:

1
sudo vim /etc/NetworkManager/conf.d/default-wifi-powersave-on.conf

将wifi.powersave = 3修改为wifi.powersave = 2
数值解释:

1
2
3
4
NM_SETTING_WIRELESS_POWERSAVE_DEFAULT (0): use the default value
NM_SETTING_WIRELESS_POWERSAVE_IGNORE (1): don't touch existing setting
NM_SETTING_WIRELESS_POWERSAVE_DISABLE (2): disable powersave
NM_SETTING_WIRELESS_POWERSAVE_ENABLE (3): enable powersave

三、卸载双系统

通过给windows EFI启动分区分配盘符,删除其中的ubuntu引导,具体操作如下:

以管理员身份打开cmd,输入diskpart

1
2
3
4
5
list disk						;查看磁盘
select disk 1
list partition ;查看分区
select partition 1 ;启动分区一般为200多M
assign letter=F

cd进去,删除/EFI/ubuntu文件夹

1
remove letter=F	;删除原先分配的盘符

然后在磁盘管理,删除ubuntu的硬盘

备注:pppop连接命令:

1
2
3
sudo pppoeconf
sudo pon dsl-provider
sudo poff

Android-6.0.0_r1源码分析-Java层加载Dex和类的过程

PROLOGUE

1
2
3
4
5
6
7
8
9
10
private Dynamic dynamic;

DexClassLoader dexClassLoader = new DexClassLoader(dexPath,optimizedDirectory, libraryPath, getClassLoader());
Class Clazz = dexClassLoader.loadClass("com.example.myapplication.Dynamic");
dynamic = (Dynamic) Clazz.newInstance();

dynamic.sayHello();

Method m = Clazz.getDeclaredMethod("sayHello");
m.invoke();

在android中,可以通过上面两行代码动态加载dex文件,并加载其中的类,调用类中的方法。记录一下动态加载Dex和类的过程。

动态加载的基础是ClassLoader—类加载器,ClassLoader 就是专门用来处理类加载工作的,一个运行中的 APP 不仅只有一个类加载器。

在 Java 中,只有当两个实例的类名、包名以及加载其的 ClassLoader 都相同,才会被认为是同一种类型

一、创建类加载器的实例

在Android中,ClassLoader是一个抽象类,定义为:public abstract class ClassLoader
在实际开发中,一般使用其子类DexClassLoaderPathClassLoader来创建类加载器实例,进而加载类,需要说明的是,这两个类都继承于BaseDexClassLoaderBaseDexClassLoader又继承于ClassLoader,他们之间的关系如下,图片来自gityuan

img

1
2
3
public class DexClassLoader extends BaseDexClassLoader
public class PathClassLoader extends BaseDexClassLoader
public class BaseDexClassLoader extends ClassLoader

DexClassLoader类和PathClassLoader类的区别在下面分析的时候会说

首先看他这两个类的构造函数:

DexClassLoader

1
2
3
4
5
6
/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java

public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}

PathClassLoader

1
2
3
4
5
6
7
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}

这两个类的构造函数都直接使用了父类的构造函数,但是传参不同。
BaseDexClassLoader构造函数:

1
2
3
4
5
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}

使用super(parent)创建了对象,然后使用makePathElements创建了DexPathList对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
......
this.definingContext = definingContext;
// save dexPath for BaseDexClassLoader
this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions);
......
}

/**
* Makes an array of dex/resource path elements, one per element of the given array.
*/
private static Element[] makePathElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions) {
List<Element> elements = new ArrayList<>();
/*
* Open all files and load the (direct or contained) dex files up front.
*/
for (File file : files) {
File zip = null;
File dir = new File("");
DexFile dex = null;
String path = file.getPath();
String name = file.getName();
......
......
else if (file.isFile()) {
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
dex = loadDexFile(file, optimizedDirectory);
}
}//endif if (file.isFile())

if ((zip != null) || (dex != null)) {
elements.add(new Element(dir, false, zip, dex));
}
}//endfor for (File file : files)
return elements.toArray(new Element[elements.size()]);
}


/**
* Constructs a {@code DexFile} instance, as appropriate depending
* on whether {@code optimizedDirectory} is {@code null}.
*/
private static DexFile loadDexFile(File file, File optimizedDirectory)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0);
}
}


/**
* Converts a dex/jar file path and an output directory to an
* output file path for an associated optimized dex file.
*/
private static String optimizedPathFor(File path,
File optimizedDirectory) {
/*
* Get the filename component of the path, and replace the
* suffix with ".dex" if that's not already the suffix.
*/
String fileName = path.getName();
if (!fileName.endsWith(DEX_SUFFIX)) {
int lastDot = fileName.lastIndexOf(".");
if (lastDot < 0) {
fileName += DEX_SUFFIX;
} else {
StringBuilder sb = new StringBuilder(lastDot + 4);
sb.append(fileName, 0, lastDot);
sb.append(DEX_SUFFIX);
fileName = sb.toString();
}
}
File result = new File(optimizedDirectory, fileName);
return result.getPath();
}

上面4个函数

DexPathList构造函数,将整个dexPath路径分成包含多个Dex文件路径的list作为参数传给了makePathElements。

makePathElements根据dex的path,将所有的dex都封装在了数组中,然后调用loadDexFile加载dex文件。

loadDexFile创建DexFile对象。若optimizedDirectory不为NULL, 将dex文件生成在optimizedDirectory路径中,使用DexFile类的loadDex函数创建对象;若optimizedDirectory为NULL,那么会直接使用 dex 文件原有的路径来创建 DexFile对象。

optimizedPathFor主要处理了源文件后缀的问题,让其后缀为.dex。当optimizedDirectory不为NULL时,在optimizedDirectory目录创建新文件,并将目录返回。

DexClassLoader类和PathClassLoader类的区别

DexClassLoader类和PathClassLoader类的区别主要在于构造对象时optimizedDirectory是否为NULL。

由于dex文件被包含在APK或者Jar文件中,因此在装载目标类之前需要先从APK或Jar文件中解压出dex文件,optimizedDirectory 必须是一个内部存储路径,optimizedDirectory参数就是指定解压出的dex文件存放的路径,PathClassLoader类构造对象时optimizedDirectory参数为空,则说明其不需要解压提取dex文件,所以其直接加载内部的dex文件;而DexClassLoader类可以指定optimizedDirectory参数,说明其需要从外部解压提取dex文件。总结如下:

DexClassLoader:能够加载未安装的jar/apk/dex
PathClassLoader:加载Android系统类和系统中应用的类

回到makePathElements函数,将DexFile对象和路径等信息封装成了数组返回给了DexPathList构造函数

1
2
    elements.add(new Element(dir, false, zip, dex));
return elements.toArray(new Element[elements.size()]);

然后在BaseDexClassLoader函数中通过DexPathList类的实例对象pathList保存了这些DexFile对象

1
private final DexPathList pathList;

二、加载类的过程

JVM 中 ClassLoader 通过 defineClass 方法加载 jar 里面的 Class,而 Android 中这个方法被弃用了。

1
2
3
4
5
@Deprecated
protected final Class<?> defineClass(byte[] classRep, int offset, int length)
throws ClassFormatError {
throw new UnsupportedOperationException("can't load this type of class file");
}

取代的是loadClass方法。第一节中通过实例化类加载器ClassLoader,创建了DexFile对象,并把对象保存在了pathList实例对象中。在Android中,使用loadClass方法加载一个类,这个方法定义在ClassLoader类中,其派生类没有重写该方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/libcore/libart/src/main/java/java/lang/ClassLoader.java

/**
* Loads the class with the specified name. Invoking this method is
* equivalent to calling {@code loadClass(className, false)}.
*/
public Class<?> loadClass(String className) throws ClassNotFoundException {
return loadClass(className, false);
}

/**
* Loads the class with the specified name, optionally linking it after
* loading.
*/
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
ClassNotFoundException suppressed = null;
try {
clazz = parent.loadClass(className, false);
} catch (ClassNotFoundException e) {
suppressed = e;
}
if (clazz == null) {
try {
clazz = findClass(className);
} catch (ClassNotFoundException e) {
e.addSuppressed(suppressed);
throw e;
}
}
}
return clazz;
}

ClassLoader 双亲委派模型加载类的作用

上面第二个loadClass方法使用了双亲委派模型,loadClass在加载一个类时

1、判断当前的类加载器ClassLoader是否加载过此类,有就直接返回
2、若未加载过当前类,就去查询Parent是否加载过此类
3、如果继承路线上的ClassLoader都没有加载,则调用findClass去加载这个类
即如果一个类被位于树根的ClassLoader加载过,那么这个类在一个虚拟机的运行周期内永远不会被重新加载。

作用

一个Class的标识为包名PackageName+类名ClassName+类加载器ClassLoader

首先是共享功能,一些 Framework 层级的类一旦被顶层的 ClassLoader 加载过就缓存在内存里面,以后任何地方用到都不需要重新加载。

除此之外还有隔离功能,不同继承路线上的 ClassLoader 加载的类肯定不是同一个类,这样的限制避免了用户自己的代码冒充核心类库的类访问核心类库包可见成员的情况。这也好理解,一些系统层级的类会在系统初始化的时候被加载,比如 java.lang.String,如果在一个应用里面能够简单地用自定义的 String 类把这个系统的 String 类给替换掉,那将会有严重的安全问题。

loadClass方法调用了findClass方法,ClassLoader类的findClass方法会报异常,而BaseDexClassLoader 重载了这个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}

上面在创建类构造器的过程中,已经创建好了所有的DexFile对象,并封装在了BaseDexClassLoader类中的一个实例对象pathList中:

1
private final DexPathList pathList;

BaseDexClassLoader的findClass方法直接调用了DexPathList类的findClass方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

/**
* Finds the named class in one of the dex files pointed at by
* this instance. This will find the one in the earliest listed
* path element. If the class is found but has not yet been
* defined, then this method will define it in the defining
* context that this instance was constructed with.
*/
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}

在DexPathList类的findClass方法中,遍历其dexElements变量中保存的DexFile对象,这个dexElements变量在DexPathList类的构方法中被初始化,由makePathElements方法生成,保存了DexFile对象。

然后接着调用了DexFile对象的loadClassBinaryName方法,我们找到DexFile类的该方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/libcore/dalvik/src/main/java/dalvik/system/DexFile.java

public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, suppressed);
}

private static Class defineClass(String name, ClassLoader loader, Object cookie,
List<Throwable> suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}

loadClassBinaryName方法是对同类下defineClass方法的封装,在defineClass方法内,又调用了native层的defineClassNative方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/art/runtime/native/dalvik_system_DexFile.cc

static jclass DexFile_defineClassNative(JNIEnv* env, jclass, jstring javaName, jobject javaLoader,jobject cookie) {
std::unique_ptr<std::vector<const DexFile*>> dex_files = ConvertJavaArrayToNative(env, cookie);
ScopedUtfChars class_name(env, javaName);
......
......
const std::string descriptor(DotToDescriptor(class_name.c_str()));
const size_t hash(ComputeModifiedUtf8Hash(descriptor.c_str()));
for (auto& dex_file : *dex_files) {
const DexFile::ClassDef* dex_class_def = dex_file->FindClassDef(descriptor.c_str(), hash);
if (dex_class_def != nullptr) {
ScopedObjectAccess soa(env);
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
class_linker->RegisterDexFile(*dex_file);
StackHandleScope<1> hs(soa.Self());
Handle<mirror::ClassLoader> class_loader(
hs.NewHandle(soa.Decode<mirror::ClassLoader*>(javaLoader)));
mirror::Class* result = class_linker->DefineClass(soa.Self(), descriptor.c_str(), hash,class_loader, *dex_file, *dex_class_def);
}
}
return nullptr;
}

DexFile_defineClassNative方法中。可以看到通过调用Runtime类的静态成员函数Current获得了Runtime单例,在ART虚拟机进程中,存在着一个Runtime单例,用来描述ART运行时。获得了这个单例之后,就可以调用它的成员函数GetClassLinker来获得一个ClassLinker对象。ClassLinker对象是在创建ART虚拟机的过程中创建的,用来加载类以及链接类方法。然后调用了class_linker的DefineClass来获得class对象并返回,至此类加载就完成了。


NDK开发学习记录

记录式学习,内容可能不全,用到知识就会记录下来,方便之后的查阅和复习。

JNIEnv的指针会被JNI传入到本地方法的实现函数中来对Java端的代码进行操作。

一、Native函数的注册

注册native函数的具体方法不同,会导致系统在运行时采用不同的方式来寻找这些native方法。

1、Native函数静态注册

静态注册就是根据函数名来遍历Java和JNI函数之间的关联,而且要求JNI层函数的名字必须遵循特定的格式。

1
2
3
4
在java层的native方法声明:
public static native int modifyBytecode();
javah生成头文件:
javah cn.pollux.modifydalvikbytecode.MainActivity

在头文件内会有native层的方法声明

1
2
JNIEXPORT jint JNICALL Java_cn_pollux_modifydalvikbytecode_MainActivity_modifyBytecode
(JNIEnv *, jclass);

JNIEXPORT:说明该函数时导出函数,在java层可以直接调用
JNICALL

总结静态注册的流程:
1、编写包含native方法的Java类
2、使用Javah命令生成.h头文件
3、编写代码实现头文件中的方法

2、Native函数动态注册

动态注册,即通过RegisterNatives方法把C/C++中的方法映射到Java中的native方法声明。

当用System.loadLibarary()方法加载so库的时候,虚拟机会首先调用JNI_OnLoad函数,这个函数的作用是告诉虚拟机此C库使用的是哪一个JNI版本,默认最初的JNI 1.1版本。所以可以在JNI_OnLoad函数中,注册JNI函数。

在Java层声明一个native函数

1
private native void testJava(String str);

在native层的JNI_OnLoad函数中将Java层的testJava函数与Native层的testNative函数绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
jint JNI_OnLoad(JavaVM* vm, void* reserved){
JNIEnv* env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
LOGE("This jni version is not supported");
return JNI_VERSION_1_6;
}
if (!registerNativeMethod(env)) {
LOGE("Unable to register native method: maybe the java has not loaded");
// I don't want the process crash, just continue
env->ExceptionClear();
}
return JNI_VERSION_1_6;
}

通过GetEnv函数获取JNIEnv结构体指针,JNIEnv结构体指向了一个函数表,该函数表指向了对应的JNI函数,我们通过这些JNI函数实现JNI编程,比如在jni.h中

1
2
3
4
5
6
typedef const struct JNINativeInterface* JNIEnv;

struct JNINativeInterface {
......
jclass (*FindClass)(JNIEnv*, const char*);
......

JNIEnv是指向JNINativeInterface结构体的指针,env是指向JNIEnv变量的指针,所以env是指向JNINativeInterface结构体指针的指针。

在看下registerNativeMethod函数内容

1
2
3
4
5
6
7
8
9
10
bool registerNativeMethod(JNIEnv *env){
auto clazz = env->FindClass("com/pollux/jnil/JNIDemo");
JNINativeMethod natives[] = {
{"testJava", "(Ljava/lang/String;)V", (void*)testNative}
};
if (env->RegisterNatives(clazz, natives,
sizeof(natives)/sizeof(JNINativeMethod))!= JNI_OK) {
env->ExceptionClear();
}
}

被动态注册的函数

1
2
3
static void testNative(JNIEnv *env, jobject, jlong handle) {
LOGI("JNI", "dynamic register");
}

RegisterNatives第二个参数为JNINativeMethod类型的数组,JNI允许通过一个函数映射表,注册给JVM,JVM通过函数映射表来查找调用相应的函数。这个函数映射表就是RegisterNatives数组,定义如下:

1
2
3
4
5
typedef struct { 
const char* name; //Java层函数名
const char* signature; //Java层函数签名
void* fnPtr; //Native层函数名(函数指针)
} JNINativeMethod;

因为Java支持重载,所以需要函数签名指定参数与返回值。

总结:

1、在Java层声明一个函数
2、在Native层的JNI_Onload函数中注册函数

二、 Java层调用Native层方法

三、Native层调用Java层代码

1、获取jclass

jclass是JNI中设定的对应于java层Class的变量,无论jclass、jmethod、jobject,出于安全的考虑,他们都是间接引用,这些变量定义在jni.h文件中

JNIEnv有三种方法获得jclass

1
2
3
jclass FindClass(const char* name)
jclass GetObjectClass(jobject obj)
jclass GetSuperclass(jclass clazz)

2、调用Java类的成员方法

关键API:

1
2
3
4
5
6
7
8
jclass FindClass(const char* name)
jobject NewObject(jclass clazz, jmethodID methodID, ...)
//获取方法ID
jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
//调用方法
NativeType CallStatic<type>Method(JNIEnv *env, jobject obj,jmethodID methodID, ...);
NativeType Call<type>Method(JNIEnv *env, jobject obj,jmethodID methodID, ...);

调用类的静态方法

1
2
3
jclass jclazz = env->FindClass("com/example/myapplication/JNITest");
jmethodID statictest_mid= env->GetStaticMethodID(jclazz,"static_test","()V");
env->CallStaticVoidMethod(jclazz,statictest_mid);

调用类的实例方法

1
2
3
4
5
6
7
//实例化对象
jclass jclazz = env->FindClass("com/example/myapplication/JNITest");
jmethodID construction_mid =env->GetMethodID(jclazz,"<init>", "()V");
jobject JNITest_obj = env->NewObject(jclazz,construction_mid);
//调用方法
jmethodID test_mid=env->GetMethodID(jclazz,"test", "(ILjava/lang/String;)Ljava/lang/String;");
jstring b =(jstring)env->CallObjectMethod(JNITest_obj,test_mid,4,env->NewStringUTF("bbbb"));

3、访问和设置Java类的成员变量

关键API:

1
2
3
4
jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)
void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value)
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
void SetIntField(jobject obj, jfieldID fieldID, jint value)

访问和设置类的静态变量

1
2
3
jfieldID fids = env->GetStaticFieldID(jclazz,"staticNum","I");
env->SetStaticIntField(jclazz,fids,34);
jint n2= env->GetStaticIntField(jclazz,fids);

访问和设置类的实例变量

1
2
3
jfieldID fid = env->GetFieldID(jclazz,"age","I");
env->SetIntField(JNITest_obj,fid,33);
jint n1 = env->GetIntField(JNITest_obj,fid);

CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
native-lib

# Sets the library as a shared library.
SHARED

# Provides a relative path to your source file(s).
native-lib.cpp)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
log-lib

# Specifies the name of the NDK library that
# you want CMake to locate.
log)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
native-lib

# Links the target library to the log library
# included in the NDK.
${log-lib})

初试反调试

理论知识不是很多,很多操作,记录一下,样本是AliCrackme_2.apk

反调试中有一个方法是循环检查进程的status字段,如果进程被ptrace后,其TracerPid为ptrace进程的pid

1
2
3
4
5
6
7
8
9
10
11
root@hammerhead:/ # cat /proc/7063/status
Name: llux.smalilearn
State: t (tracing stop)
Tgid: 7063
Pid: 7063
PPid: 757
TracerPid: 7099
......

root@hammerhead:/ # ps |grep 7099
root 7099 7094 16964 11840 ffffffff b6efe854 S ./android_server

1、添加android:debuggable = “true”

修改AndroidManifest.xml在manifest标签下加上android:debuggable = "true",否则在用jdb附加进程时会报错,附加不上进程。

重新打包:

1
java -jar signapk.jar testkey.x509.pem testkey.pk8 AliCrackme_2.apk AliCrackme_2.sig.apk

2、使用am命令以debug方式启动程序,此时程序处于等待Debugger状态

1
adb shell am start -D -n com.yaotong.crackme/.MainActivity

3、在安卓端启动android_server,程序在安装目录的dbgsrv文件夹下

4、为IDA设置端口转发

1
adb forward tcp:23946 tcp:23946

5、IDA附加进程,并设置加载库的断点

image-20191202160324614

6、打开ddms,使用jdb附加进程

8700是ddms开启的端口转发

1
jdb -attach 127.0.0.1:8700

返回IDA,运行程序,程序就会断在linker.so中,linker是用于加载so文件的模块
image-20191202161144065

双开IDA,计算JNI_Onload函数的地址,到该处下断,单步到BLX R7时退出JNI_Onload函数

image-20191202161354035

R7的值为pthread_create,回看so的伪代码,可以猜测程序开启新线程去循环查询TracerPid的值是否为0,定位到BLX R7的偏移为1C58,选中该行指令,CTRL+ALT+K将其patch为空指令

image-20191202163955073

ARM指令集中的NOP(0xe1a00000)是一条伪指令,编译系统用一条MOV r0, r0, lsl #0(0xe1a00000)指令替代其执行,设为ANDEQ r0,r0,r0,即可生成空指令

保存后,重新打包签名,IDA可以正常调试了


手机和AS中debug环境配置问题小记

一、在AS中尝试调试native代码,报错

1、报错:

1
Attention! No symbol directories found - please check your native debug configuration

解决:

在app/build.gradle的android标签下,添加如下内容,关闭debug模式的混淆:

1
2
3
4
5
buildTypes {
debug {
minifyEnabled false
}
}

2、报错:

启动调试时直接退出,报错内容为:

1
2
Process finished with exit code 0
com.intellij.execution.ExecutionFinishedException: Execution finished

解决:

之前host文件被我改动过,将绑定localhost重新绑定为127.0.0.1

1
127.0.0.1 localhost

二、手机debug模式的问题

问题:

在ddms中只能看到被debug的程序

解决:

输入如下命令

1
adb shell getprop ro.debuggable

若ro.debuggable = 0,说明这个rom是user版本,只能调试debug程序
若ro.debuggable = 1,说明这个rom是userdebug版本,可以调试系统所有进程

有两种方法解决:

1、安装Xposed的xinstaller模块,在模块的其他设置中开启调试应用功能
xinstaller 下载地址:https://repo.xposed.info/module/com.pyler.xinstaller

2、从ROM中提取boot.img,解包,修改default.prop,将ro.debuggable=0修改为ro.debuggable=1,重新打包
参考文章:https://bbs.pediy.com/thread-197334.htm


nexus5环境配置

nexus5配置环境,记录一下。

工具版本

  1. 官方镜像:hammerhead-lrx22c-factory-3b22f481.zip
  2. Auto Root:CF-Auto-Root-hammerhead-hammerhead-nexus5.zip
  3. 第三方Recovery:twrp-3.3.1-0-hammerhead.img
  4. Xposed应用:xposed-installer-3-1-5.apk
  5. Xposed框架:xposed-v89-sdk21-arm.zip
  6. Xposed模块XInstaller:com.pyler.xinstaller_v500_128e2a.apk
  7. adbd Insecure:adbd-Insecure-v2.00.apk

1、刷入官方镜像

下载镜像
https://developers.google.com/android/images#hammerhead

手机进入bootloader

1
2
adb reboot bootloader
或长按关机键+音量下

使用flash-all.sh脚本刷入后无限重启,先将下载后镜像包中的image-hammerhead-lrx22c.zip解压,写一个shell脚本安装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/sh
fastboot erase cache
fastboot erase userdata
fastboot erase bootfastboot erase cache
fastboot erase recovery
fastboot erase system
fastboot erase userdata
fastboot flash bootloader bootloader-hammerhead-hhz12d.img
fastboot reboot-bootloader
fastboot flash radio radio-hammerhead-m8974a-2.0.50.2.22.img
fastboot reboot-bootloader
fastboot flash recovery ./image-hammerhead-lrx22c/recovery.img
fastboot flash boot ./image-hammerhead-lrx22c/boot.img
fastboot flash system ./image-hammerhead-lrx22c/system.img
fastboot flash cache ./image-hammerhead-lrx22c/cache.img
fastboot flash userdata ./image-hammerhead-lrx22c/userdata.img

然后重启手机

2、ROOT

下载Auto Root
https://download.chainfire.eu/363/CF-Root/CF-Auto-Root/CF-Auto-Root-hammerhead-hammerhead-nexus5.zip

1
2
3
adb reboot bootloader
chmod +x root-mac.sh
./root-mac.sh

3、twrp

下载Twrp
https://twrp.me/lg/lgnexus5.html

1
2
3
4
adb reboot bootloader
fastboot flash recovery twrp-3.3.1-0-hammerhead.img
fastboot reboot
adb reboot recovery

4、Xposed

下载安装安装包

1
adb install xposed-installer-3-1-5.apk

android5.0以上需要安装额外的框架
https://forum.xda-developers.com/showthread.php?t=3034811

下载对应的框架
https://dl-xda.xposed.info/framework/sdk21/arm/xposed-v89-sdk21-arm.zip

官方推荐Twrp安装
进入Twrp->Advanced->ADB sideload

1
2
3
adb slidload adb sideload xposed-v89-sdk21-arm.zip

adb shell flash-archive.sh xposed-v89-sdk21-arm.zip

5、安装adbd Insecure

让adb shell命令直接变成root权限

6、安装Xposed的Xinstaller模块

https://repo.xposed.info/module/com.pyler.xinstaller

不需要修改boot.img,开启系统调试


通过在native层动态修改内存中的Dalvik指令来学习Dex文件格式

1. Prologue

只看Dex文件格式感觉会很枯燥,所以想通过动手的方式去理解Dex文件中的相关数据的作用,以及互相怎么关联起来的。实现的目标是通过so库动态将内存中的加法指令修改成乘法指令。具体的学习过程是学习了姜维的博客,弄懂后觉得这个文章很适合用来学习Dex文件格式,所以参考了网上有关Dex文件格式的文章,打算用自己的思路记录一下学习的过程。部分内容可能会赘述姜维的文章,但是我会穿插自己遇到的难懂的部分和一些内容自己的整理。

具体来说是定义了一个类,其中有一个方法实现了加法的功能,然后在so库中,寻找到这个方法的指令代码,将其修改成减法。

1
2
3
4
5
6
7
public class TestAdd {
public int add(int a,int b){
int c;
c = a + b;
return c;
}
}

2.Dex文件布局

首先说明一个下面会用到的数据结构

leb128

LEB128 ( little endian base 128 ) 格式 ,是基于 1 个 Byte 的一种不定长度的编码方式 。不定长度是指取每个字节的最高位当做标志位,若一个字节的最高位为1,则表示下个字节的内容也属于这个数值,直到一个字节的最高位为0,就相当于字符串的”\0”。比如leb128 0x0c2e。

0x0c 0x2e
0000 1100 1010 1110

可以看到高位字节的0x0c的最高位为0。
那么这个数值表示多少?转换过程如下

转换过程 数值
原始数据 0000 1100 1010 1110
因为最高位是标志位,所以删去最高位 000 1100 010 1110
按4bit从低到高重新组合 00 0110 0010 1110
结果 0x062e

这种数据格式相对于int可以有效的降低内存使用

底层代码位于:android/dalvik/libdex/leb128.h


dex文件可以分为3个部分,分别是文件头、索引区、数据区。图片来自四哥的博客。

img

文件头

img

文件头描述了整个dex文件的文件信息,各个索引分区的大小和偏移地址,这个偏移地址是相对于文件起始地址的偏移。逻辑上用header_item结构体表示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
ubyte 8-bit unsinged int
uint 32-bit unsigned int, little-endian
struct header_item
{
ubyte[8] magic;
unit checksum;
ubyte[20] signature;
uint file_size;
uint header_size;
unit endian_tag;
uint link_size;
uint link_off;
uint map_off;
uint string_ids_size; //60
uint string_ids_off; //64
uint type_ids_size;
uint type_ids_off;
uint proto_ids_size;
uint proto_ids_off;
uint method_ids_size;
uint method_ids_off;
uint class_defs_size;
uint class_defs_off;
uint data_size;
uint data_off;
}

索引区

string_ids

string_ids 区描述了 .dex 文件所有的字符串。在实例中,在已知通过类和方法名的情况下,通过遍历string_data_off指向的字符串,获得string_ids_item结构体数组的下标index,这个index和后面的type_ids区、method_ids区密切相关。

1
2
3
struct string_ids_item{
uint string_data_off;
}

在实例中,通过字符串找到string_ids_item的结构体数组下标代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int getStrIdx(int search_start_position, char *target_string,int size)
{
int index;
int stringidsoff;
int stringdataoff;
int *stringaddress;
int string_num_mutf8;

//header_item->string_ids_size
if(*(int *)(search_start_position + 56))
{
index = 0;
//header_item->string_ids_off
stringidsoff = search_start_position + *(int *)(search_start_position+60);
while(1) {
stringdataoff = *(int *)stringidsoff;
stringidsoff +=4;
stringaddress = (int *)(search_start_position + stringdataoff);
string_num_mutf8 = 0;
if( readUleb128(stringaddress,(int)&string_num_mutf8) == size
&& !strncmp((char *)stringaddress + string_num_mutf8,target_string,size))
break;
++index;
if(*(int *)(search_start_position+56) <= index ) {
index = -1;
break;
}
}
}else {
index = -1;
}
return index;
}

type_ids

type_ids区索引了 dex 文件里的所有数据类型 ,包括 class 类型 ,数组类型(array types)和基本类型(primitive types) ,其中descriptor_idx对应于上面提到的string_ids_item结构体数组的下标。

1
2
3
struct type_ids_item{
uint descriptor_idx;
}

通过string_idx_item的下标,找到type_ids_item的下标的代码如下,后面会说明这么索引的结果是什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
signed int getTypeIdx(int search_start_position, int strIdx)
{
int typeIdsSize;
int typeIdsOff;
int typeid_to_stringid;
signed int result;
int next_typeIdsOff;
int next_typeid_to_stringid;

typeIdsSize = *(int *)(search_start_position + 64);
if ( !typeIdsSize )
return -1;
typeIdsOff = search_start_position + *(int *)(search_start_position + 68);
typeid_to_stringid = *(int *)typeIdsOff;
result = 0;
next_typeIdsOff = typeIdsOff + 4;
if ( typeid_to_stringid != strIdx )
{
while ( 1 )
{
++result;
if ( result == typeIdsSize )
break;
next_typeid_to_stringid = *(int *)next_typeIdsOff;
next_typeIdsOff += 4;
if ( next_typeid_to_stringid == strIdx )
return result;
}
return -1;
}
return result;//返回下标
}

method_ids

method_ids记录了dex文件中的方法信息,通过string_idx的下标和type_idx的下标,可以索引到指定的method_ids结构体。

1
2
3
4
5
struct method_ids_item{
ushort class_idx;
ushort proto_idx;
uint name_idx;
}

在实例中,索引指定方法的结构体,获取method_ids下标的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int  getMethodIdx(int search_start_position, int method_strIdx, int class_typeIdx)
{
int methodIdsSize;
int classIdx;
signed int result;

methodIdsSize = *(int *)(search_start_position + 88);//header_item->methodIdsSize
if ( methodIdsSize )
{
classIdx = search_start_position + *(int *)(search_start_position + 92);//header_item->methodIdsOff
result = 0;
while ( *(short *)classIdx != class_typeIdx || *(int *)(classIdx + 4) != method_strIdx )
{
++result;
classIdx += 8;
if ( result == methodIdsSize )
{result = -1;break;}
}
}
else
{
result = -1;
}
return result;
}

数据区

class_defs

1
2
3
4
5
6
7
8
9
10
11
struct class_def_item
{
uint class_idx; //-->type_ids
uint access_flags;
uint superclass_idx; //-->type_ids
uint interface_off; //-->type_list
uint source_file_idx; //-->string_ids
uint annotations_off; //-->annotation_directory_item
uint class_data_off; //-->class_data_item
uint static_value_off; //-->encoded_array_item
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int getClassItem(int search_start_position, int class_typeIdx)
{
int classDefsSize;

int classDefsOff;
int result;
int classIdx;
int count;

classDefsSize = *(int *)(search_start_position + 96);
classDefsOff = *(int *)(search_start_position + 100);
result = 0;
if (classDefsSize)
{
classIdx = search_start_position + classDefsOff;
result = classIdx;
if ( *(int *)classIdx != class_typeIdx)
{
count = 0;
while (1)
{
++count;
if (count == classDefsSize)
break;
result += 32;
if ( *(int *)(result) == class_typeIdx)
return result;
}
result = 0;
}
}
return result;//返回class_def_item结构体的地址
}

class_data_item

class_data_item结构体描述了static字段,类字段,static方法,类方法等信息。其中direct_methodsvirtual_methods是结构体数组,一个方法对应一个数组元素。这个结构体主要通过class_defs结构体的class_data_off字段索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct class_data_item
{
uleb128 static_fields_size;
uleb128 instance_fields_size;
uleb128 direct_methods_size;
uleb128 virtual_methods_size;
encoded_field static_fields[static_fields_size];
encoded_field instance_fields[instance_fields_size];
encoded_method direct_methods[direct_methods_size];
encoded_method virtual_methods[virtual_methods_size];
}
struct encoded_field
{
uleb128 filed_idx_diff;
uleb128 access_flags;
}
struct encoded_method
{
uleb128 method_idx_diff;
uleb128 access_flags;
uleb128 code_off;
}

encoded_method结构体的code_off一个指向 data 区的偏移地址 ,目标是 method 的代码实现 ,被指向的结构是code_item,而method_idx_diff字段前缀 methd_idx 表示它的值是 method_ids 的一个 下标,后缀 _diff 表示它是于另外一个 method_idx 的一个差值 ,就是相对于 encodeed_method [] 数组里上一个元素的 method_idx 的差值。
如下两个方法,第一个是构造方法,第二个是定义的direct方法:
image-20191205151233427

direct_methods
定义的直接(static、private 或构造方法的任何一个)方法;以一系列编码元素的形式表示。这些方法必须按 method_idx 以升序进行排序。

virtual_methods
定义的虚拟(非 static、private 或构造函数)方法;以一系列编码元素的形式表示。此列表不得包括继承方法,除非被此项所表示的类覆盖。这些方法必须按 method_idx 以升序进行排序。虚拟方法的 method_idx 不得与任何直接方法相同。

code_item

code_item结构体描述了某个 method 的具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
struct code_item
{
ushort registers_size;
ushort ins_size;
ushort outs_size;
ushort tries_size;
uint debug_info_off;
uint insns_size;//指令个数
ushort insns [ insns_size ];
ushort paddding; // optional
try_item tries [ tyies_size ]; // optional
encoded_catch_handler_list handlers; // optional
}

通过一个类的class_data_item地址和其方法的method_idx获取这个方法对应的code_item结构的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
int getCodeItem(int search_start_position, int class_def_item_address, int methodIdx)
{

int *classDataOff;

int staticFieldsSize;
int *classDataOff_new_start;
int instanceFieldsSize;

int directMethodsSize;
int virtualMethodSize;

int *after_skipstaticfield_address;
int *DexMethod_start_address;
int result;
int DexMethod_methodIdx;
int *DexMethod_accessFlagsstart_address;
int Uleb_bytes_read;
int tmp;

//获取DexClassData结构体的地址
classDataOff = (int *)(*(int *)(class_def_item_address + 24) + search_start_position);
LOGD(" classDataOff = %x", classDataOff);

Uleb_bytes_read = 0;
staticFieldsSize = readUleb128(classDataOff, (int)&Uleb_bytes_read);
LOGD("staticFieldsSize= %d",staticFieldsSize);

classDataOff_new_start = (int *)((char *)classDataOff + Uleb_bytes_read);
LOGD("staticFieldsSize_addr= %x",classDataOff_new_start);

instanceFieldsSize = readUleb128(classDataOff_new_start, (int)&Uleb_bytes_read);
LOGD("instanceFieldsSize= %d",instanceFieldsSize);

classDataOff_new_start = (int *)((char *)classDataOff_new_start + Uleb_bytes_read);
LOGD("instanceFieldsSize_addr= %x",classDataOff_new_start);

directMethodsSize = readUleb128(classDataOff_new_start, (int)&Uleb_bytes_read);
LOGD("directMethodsSize= %d",directMethodsSize);

classDataOff_new_start = (int *)((char *)classDataOff_new_start + Uleb_bytes_read);
LOGD("directMethod_addr= %x",classDataOff_new_start);

virtualMethodSize = readUleb128(classDataOff_new_start, (int)&Uleb_bytes_read);
LOGD("virtualMethodsSize= %d",virtualMethodSize);

after_skipstaticfield_address = skipUleb128(2 * staticFieldsSize, (int *)((char *)classDataOff_new_start + Uleb_bytes_read));
LOGD("after_skipstaticfield_address = %x", after_skipstaticfield_address);

DexMethod_start_address = skipUleb128(2 * instanceFieldsSize, after_skipstaticfield_address);
LOGD("DexMethod_start_address = %x", DexMethod_start_address);

result = 0;
if(directMethodsSize)
{
DexMethod_methodIdx = 0;
int DexMethod_methodIdx_tmp = 0;
do {

DexMethod_methodIdx_tmp = 0;
DexMethod_methodIdx = readUleb128(DexMethod_start_address, (int)&Uleb_bytes_read);
DexMethod_methodIdx_tmp = readUleb128(DexMethod_start_address, (int)&Uleb_bytes_read);

LOGD("DexMethod_direct_methodIdx = %x", DexMethod_methodIdx);
LOGD("DexMethod_direct_methodIdx_tmp = %x", DexMethod_methodIdx_tmp);

DexMethod_accessFlagsstart_address = (int *)((char *)DexMethod_start_address + Uleb_bytes_read);
if (DexMethod_methodIdx == methodIdx)
{
readUleb128(DexMethod_accessFlagsstart_address, (int)&Uleb_bytes_read);
return readUleb128((int *)((char *)DexMethod_accessFlagsstart_address + Uleb_bytes_read),
(int)&Uleb_bytes_read) + search_start_position;
}
--directMethodsSize;
DexMethod_start_address = skipUleb128(2, DexMethod_accessFlagsstart_address);
}while (directMethodsSize);
result = 0;
}

if (virtualMethodSize)
{
DexMethod_methodIdx = 0;
int DexMethod_methodIdx_tmp = 0;
do
{
DexMethod_methodIdx_tmp = 0;
DexMethod_methodIdx = readUleb128(DexMethod_start_address, (int)&Uleb_bytes_read);
DexMethod_methodIdx_tmp = readUleb128(DexMethod_start_address, (int)&Uleb_bytes_read);

LOGD("DexMethod_virtual_methodIdx = %x", DexMethod_methodIdx);
LOGD("DexMethod_virtual_methodIdx_tmp = %x", DexMethod_methodIdx_tmp);

DexMethod_accessFlagsstart_address = (int *)((char *)DexMethod_start_address + Uleb_bytes_read);
if (DexMethod_methodIdx == methodIdx)
{
readUleb128(DexMethod_accessFlagsstart_address, (int)&Uleb_bytes_read);
return readUleb128((int *)((char *)DexMethod_accessFlagsstart_address + Uleb_bytes_read), (int)&Uleb_bytes_read) + search_start_position;
}
--virtualMethodSize;
DexMethod_start_address = skipUleb128(2, DexMethod_accessFlagsstart_address);
}while ( virtualMethodSize );
result = 0;
}

return result;
}

返回的内容就是code_item结构体的地址。

3. 实例说明

结构体之间的关联

通过上面Dex文件布局的说明,总结一下通过如何通过类名和方法名,在内存中找到方法的指令代码

  1. 已知类名和方法名,通过索引区的string_idx_item结构体,获取class_strIdx和method_strIdx

    1
    2
    3
    class_strIdx = getStrIdx(search_start_position,"Lcom/pollux/dalvikbytecode/TestAdd;",
    strlen("Lcom/pollux/dalvikbytecode/TestAdd;"));
    method_strIdx = getStrIdx(search_start_position,"add", 3);
  2. 已知class_strIdx,通过索引区的type_idx_item结构体,获取class_typeIdx

    1
    class_typeIdx = getTypeIdx(search_start_position,class_strIdx);
  3. 已知class_typeIdx和method_strIdx,通过索引区的method_idx_item结构体,获取method_typeIdx

    1
    methodIdx = getMethodIdx(search_start_position, method_strIdx, class_typeIdx);
  4. 已知class_typeIdx,获取类对应的class_defs_item结构体的地址

    1
    class_def_item_address = getClassItem(search_start_position,class_typeIdx);
  5. 有了class_defs_item结构体的地址,就可以通过其class_data_off字段,获取class_data_item结构体的地址,进而通过上面获取的method_typeIdx,在encoded_method字段中找到方法对应的code_item结构体地址,而code_item则包含了这个方法实现的具体指令代码

    1
    codeItem_address = getCodeItem(search_start_position,class_def_item_address,methodIdx);

之后就是解析指令,指令代码存放在code_item中insns字段中,2字节一个元素,所以如果code_item->insns_size=3,那么指令代码的大小为3x2=6字节

首先获取指令的大小

1
2
3
4
5
6
7
char insnssize[4];
void *code_insns_size = (void*)(codeItem_address+12);
memcpy(insnssize,code_insns_size,4);
int k = 0;
for(;k<4;k++){
LOGD("size:%d",insnssize[k]);
}

接着获取具体的指令代码

1
2
3
4
5
6
7
8
9
10
void *code_insns_address;
code_insns_address = (void *)(codeItem_address+16);
LOGD("code_insns_address = %x", code_insns_address);
//这里可以先打印方法的指令
char instrans[6];//这里的6就是insns_size*2,因为short是两个字节
memcpy(instrans, code_insns_address, 6);
int i=0;
for(;i<6;i++){
LOGD("%x",instrans[i]);
}

可以看到实现加法的指令是90 00 02 03 0F 00,一条指令格式是指令码+指令操作数,通过查找Dalvik指令的格式,可以知道加法的指令是0x90,指令格式是binop vAA, vBB, vCC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
binop vAA, vBB, vCC
90: add-int
91: sub-int
92: mul-int
93: div-int
94: rem-int
95: and-int
96: or-int

A: destination register or pair (8 bits)
B: first source register or pair (8 bits)
C: second source register or pair (8 bits)

Perform the identified binary operation on the two source registers, storing the result in the first source register.

可以知道 00 是0号寄存器,02是二号寄存器,03是3号寄存器,所以只要将90修改成91就变成减法了

1
2
3
4
5
6
7
void *codeinsns_page_address =
(void *)(codeItem_address + 16 - (codeItem_address + 16) % (unsigned int)page_size);
//修改该页的读写权限
mprotect(codeinsns_page_address,page_size, PROT_READ|PROT_WRITE);
//写入对应减法的字节码
char inject[]={0x91,0x00,0x02,0x03,0x0f,0x00};
memcpy(code_insns_address,&inject,6);

4. 总结

虽然修改内存中的指令没有难度,但是可以通过这个,可以学习到Dex中各个数据之间的关系,怎么通过类名和方法名在内存中找到对应的指令。实现加法指令变成减法指令很容易,因为并没有改变指令的整体长度,如果修改复杂的指令,就有可能改变指令的长度,而Dex文件中各个数据的寻址都是相对地址,所以如果指令长度修改了,就要对可能影响到的相对地址进行修复,就会变得很复杂。


IDA notebook

1. 常用宏定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
/*

This file contains definitions used by the Hex-Rays decompiler output.
It has type definitions and convenience macros to make the
output more readable.

Copyright (c) 2007-2011 Hex-Rays

*/

#if defined(__GNUC__)
typedef long long ll;
typedef unsigned long long ull;
#define __int64 long long
#define __int32 int
#define __int16 short
#define __int8 char
#define MAKELL(num) num ## LL
#define FMT_64 "ll"
#elif defined(_MSC_VER)
typedef __int64 ll;
typedef unsigned __int64 ull;
#define MAKELL(num) num ## i64
#define FMT_64 "I64"
#elif defined (__BORLANDC__)
typedef __int64 ll;
typedef unsigned __int64 ull;
#define MAKELL(num) num ## i64
#define FMT_64 "L"
#else
#error "unknown compiler"
#endif
typedef unsigned int uint;
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned long ulong;

typedef char int8;
typedef signed char sint8;
typedef unsigned char uint8;
typedef short int16;
typedef signed short sint16;
typedef unsigned short uint16;
typedef int int32;
typedef signed int sint32;
typedef unsigned int uint32;
typedef ll int64;
typedef ll sint64;
typedef ull uint64;

// Partially defined types:
#define _BYTE uint8
#define _WORD uint16
#define _DWORD uint32
#define _QWORD uint64
#if !defined(_MSC_VER)
#define _LONGLONG __int128
#endif

#ifndef _WINDOWS_
typedef int8 BYTE;
typedef int16 WORD;
typedef int32 DWORD;
typedef int32 LONG;
#endif
typedef int64 QWORD;
#ifndef __cplusplus
typedef int bool; // we want to use bool in our C programs
#endif

// Some convenience macros to make partial accesses nicer
// first unsigned macros:
#define LOBYTE(x) (*((_BYTE*)&(x))) // low byte
#define LOWORD(x) (*((_WORD*)&(x))) // low word
#define LODWORD(x) (*((_DWORD*)&(x))) // low dword
#define HIBYTE(x) (*((_BYTE*)&(x)+1))
#define HIWORD(x) (*((_WORD*)&(x)+1))
#define HIDWORD(x) (*((_DWORD*)&(x)+1))
#define BYTEn(x, n) (*((_BYTE*)&(x)+n))
#define WORDn(x, n) (*((_WORD*)&(x)+n))
#define BYTE1(x) BYTEn(x, 1) // byte 1 (counting from 0)
#define BYTE2(x) BYTEn(x, 2)
#define BYTE3(x) BYTEn(x, 3)
#define BYTE4(x) BYTEn(x, 4)
#define BYTE5(x) BYTEn(x, 5)
#define BYTE6(x) BYTEn(x, 6)
#define BYTE7(x) BYTEn(x, 7)
#define BYTE8(x) BYTEn(x, 8)
#define BYTE9(x) BYTEn(x, 9)
#define BYTE10(x) BYTEn(x, 10)
#define BYTE11(x) BYTEn(x, 11)
#define BYTE12(x) BYTEn(x, 12)
#define BYTE13(x) BYTEn(x, 13)
#define BYTE14(x) BYTEn(x, 14)
#define BYTE15(x) BYTEn(x, 15)
#define WORD1(x) WORDn(x, 1)
#define WORD2(x) WORDn(x, 2) // third word of the object, unsigned
#define WORD3(x) WORDn(x, 3)
#define WORD4(x) WORDn(x, 4)
#define WORD5(x) WORDn(x, 5)
#define WORD6(x) WORDn(x, 6)
#define WORD7(x) WORDn(x, 7)

// now signed macros (the same but with sign extension)
#define SLOBYTE(x) (*((int8*)&(x)))
#define SLOWORD(x) (*((int16*)&(x)))
#define SLODWORD(x) (*((int32*)&(x)))
#define SHIBYTE(x) (*((int8*)&(x)+1))
#define SHIWORD(x) (*((int16*)&(x)+1))
#define SHIDWORD(x) (*((int32*)&(x)+1))
#define SBYTEn(x, n) (*((int8*)&(x)+n))
#define SWORDn(x, n) (*((int16*)&(x)+n))
#define SBYTE1(x) SBYTEn(x, 1)
#define SBYTE2(x) SBYTEn(x, 2)
#define SBYTE3(x) SBYTEn(x, 3)
#define SBYTE4(x) SBYTEn(x, 4)
#define SBYTE5(x) SBYTEn(x, 5)
#define SBYTE6(x) SBYTEn(x, 6)
#define SBYTE7(x) SBYTEn(x, 7)
#define SBYTE8(x) SBYTEn(x, 8)
#define SBYTE9(x) SBYTEn(x, 9)
#define SBYTE10(x) SBYTEn(x, 10)
#define SBYTE11(x) SBYTEn(x, 11)
#define SBYTE12(x) SBYTEn(x, 12)
#define SBYTE13(x) SBYTEn(x, 13)
#define SBYTE14(x) SBYTEn(x, 14)
#define SBYTE15(x) SBYTEn(x, 15)
#define SWORD1(x) SWORDn(x, 1)
#define SWORD2(x) SWORDn(x, 2)
#define SWORD3(x) SWORDn(x, 3)
#define SWORD4(x) SWORDn(x, 4)
#define SWORD5(x) SWORDn(x, 5)
#define SWORD6(x) SWORDn(x, 6)
#define SWORD7(x) SWORDn(x, 7)


// Helper functions to represent some assembly instructions.

#ifdef __cplusplus

// Fill memory block with an integer value
inline void memset32(void *ptr, uint32 value, int count)
{
uint32 *p = (uint32 *)ptr;
for ( int i=0; i < count; i++ )
*p++ = value;
}

// Generate a reference to pair of operands
template<class T> int16 __PAIR__( int8 high, T low) { return ((( int16)high) << sizeof(high)*8) | uint8(low); }
template<class T> int32 __PAIR__( int16 high, T low) { return ((( int32)high) << sizeof(high)*8) | uint16(low); }
template<class T> int64 __PAIR__( int32 high, T low) { return ((( int64)high) << sizeof(high)*8) | uint32(low); }
template<class T> uint16 __PAIR__(uint8 high, T low) { return (((uint16)high) << sizeof(high)*8) | uint8(low); }
template<class T> uint32 __PAIR__(uint16 high, T low) { return (((uint32)high) << sizeof(high)*8) | uint16(low); }
template<class T> uint64 __PAIR__(uint32 high, T low) { return (((uint64)high) << sizeof(high)*8) | uint32(low); }

// rotate left
template<class T> T __ROL__(T value, uint count)
{
const uint nbits = sizeof(T) * 8;
count %= nbits;

T high = value >> (nbits - count);
value <<= count;
value |= high;
return value;
}

// rotate right
template<class T> T __ROR__(T value, uint count)
{
const uint nbits = sizeof(T) * 8;
count %= nbits;

T low = value << (nbits - count);
value >>= count;
value |= low;
return value;
}

// carry flag of left shift
template<class T> int8 __MKCSHL__(T value, uint count)
{
const uint nbits = sizeof(T) * 8;
count %= nbits;

return (value >> (nbits-count)) & 1;
}

// carry flag of right shift
template<class T> int8 __MKCSHR__(T value, uint count)
{
return (value >> (count-1)) & 1;
}

// sign flag
template<class T> int8 __SETS__(T x)
{
if ( sizeof(T) == 1 )
return int8(x) < 0;
if ( sizeof(T) == 2 )
return int16(x) < 0;
if ( sizeof(T) == 4 )
return int32(x) < 0;
return int64(x) < 0;
}

// overflow flag of subtraction (x-y)
template<class T, class U> int8 __OFSUB__(T x, U y)
{
if ( sizeof(T) < sizeof(U) )
{
U x2 = x;
int8 sx = __SETS__(x2);
return (sx ^ __SETS__(y)) & (sx ^ __SETS__(x2-y));
}
else
{
T y2 = y;
int8 sx = __SETS__(x);
return (sx ^ __SETS__(y2)) & (sx ^ __SETS__(x-y2));
}
}

// overflow flag of addition (x+y)
template<class T, class U> int8 __OFADD__(T x, U y)
{
if ( sizeof(T) < sizeof(U) )
{
U x2 = x;
int8 sx = __SETS__(x2);
return ((1 ^ sx) ^ __SETS__(y)) & (sx ^ __SETS__(x2+y));
}
else
{
T y2 = y;
int8 sx = __SETS__(x);
return ((1 ^ sx) ^ __SETS__(y2)) & (sx ^ __SETS__(x+y2));
}
}

// carry flag of subtraction (x-y)
template<class T, class U> int8 __CFSUB__(T x, U y)
{
int size = sizeof(T) > sizeof(U) ? sizeof(T) : sizeof(U);
if ( size == 1 )
return uint8(x) < uint8(y);
if ( size == 2 )
return uint16(x) < uint16(y);
if ( size == 4 )
return uint32(x) < uint32(y);
return uint64(x) < uint64(y);
}

// carry flag of addition (x+y)
template<class T, class U> int8 __CFADD__(T x, U y)
{
int size = sizeof(T) > sizeof(U) ? sizeof(T) : sizeof(U);
if ( size == 1 )
return uint8(x) > uint8(x+y);
if ( size == 2 )
return uint16(x) > uint16(x+y);
if ( size == 4 )
return uint32(x) > uint32(x+y);
return uint64(x) > uint64(x+y);
}

#else
// The following definition is not quite correct because it always returns
// uint64. The above C++ functions are good, though.
#define __PAIR__(high, low) (((uint64)(high)<<sizeof(high)*8) | low)
// For C, we just provide macros, they are not quite correct.
#define __ROL__(x, y) __rotl__(x, y) // Rotate left
#define __ROR__(x, y) __rotr__(x, y) // Rotate right
#define __CFSHL__(x, y) invalid_operation // Generate carry flag for (x<<y)
#define __CFSHR__(x, y) invalid_operation // Generate carry flag for (x>>y)
#define __CFADD__(x, y) invalid_operation // Generate carry flag for (x+y)
#define __CFSUB__(x, y) invalid_operation // Generate carry flag for (x-y)
#define __OFADD__(x, y) invalid_operation // Generate overflow flag for (x+y)
#define __OFSUB__(x, y) invalid_operation // Generate overflow flag for (x-y)
#endif

// No definition for rcl/rcr because the carry flag is unknown
#define __RCL__(x, y) invalid_operation // Rotate left thru carry
#define __RCR__(x, y) invalid_operation // Rotate right thru carry
#define __MKCRCL__(x, y) invalid_operation // Generate carry flag for a RCL
#define __MKCRCR__(x, y) invalid_operation // Generate carry flag for a RCR
#define __SETP__(x, y) invalid_operation // Generate parity flag for (x-y)

// In the decompilation listing there are some objects declarared as _UNKNOWN
// because we could not determine their types. Since the C compiler does not
// accept void item declarations, we replace them by anything of our choice,
// for example a char:

#define _UNKNOWN char

#ifdef _MSC_VER
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#endif

2. 快捷键

Shirt+F12,打开字符串内容窗口
Ctrl+S,有两个用途,在正常打开so文件的IDA View视图的时候,可以查看so对应的Segement信息;当在调试页面的时候,ctrl+s可以快速定位到我们想要调试的so文件映射到内存的地址
G快捷键:在IDA调试页面的时候,我们可以使用S键快速跳转到指定的内存位置
调试快捷键:F8单步调试,F7单步进入调试,F9快捷键运行


使用frida Hook动态加载Dex

复现了2018DDCTF 的一道安卓题HelloBabyDex,参考了网上的很多资料,学到了很多知识,记录一下使用frida Hook动态加载Dex去解这道题过程中学到的东西,期间还补充了Java关于反射机制的知识…..

POINT

  1. Hook动态加载的Dex的类方法
  2. Hook重载的方法
  3. 使用Java.cast进行类型转换
  4. 使用Java.array创建数组
  5. java反射机制中的实例化对象

PROLOGUE

热修复其实是使用DexClassLoader方法去动态加载文件,然后通过反射机制去调用类方法或成员变量,而DexClassLoader方法真正使用的是其父类的父类中的loadClass方法,那就可以去Hook DexClassLoader这个类下的loadClass方法。

Hook重载的方法

但是loadClass有2个重载方法,在frida中使用overload指定Hook的重载方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
......
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}

Hook只有一个参数为String类型的方法:

1
2
var dexclassLoader = Java.use("dalvik.system.DexClassLoader");
dexclassLoader.loadClass.overload('java.lang.String').implementation = function(name)

类型转换

这个name就是动态加载的类名,即 cn.chaitin.geektan.crackme.MainActivityPatch,当name匹配成功时,返回一个类对象class cn.chaitin.geektan.crackme.MainActivityPatch,按理说利用反射机制,可以使用这个类对象去实例化对象,但是尝试后不行,需要进行类型转换

1
2
var ClassUse = Java.use("java.lang.Class");
var hookClassCast = Java.cast(hookClass,ClassUse);

这样就获得了一个指向MainActivityPatch的类对象

通过类对象实例化对象

有了类对象,就可以通过类对象的 getDeclaredConstructor(Class<?>...ParametersType)方法获得指定参数的构造器,即构造函数对象。函数的参数是类型为类对象的数组。查看动态加载类中MainActivityPatch的构造函数,参数是Object类型。

1
2
3
public MainActivityPatch(Object obj) {
this.originClass = (MainActivity) obj;
}

通过frida的Java.array方法去构造Object类型的数组,语法是

1
Java.array('type',[value1,value2,....]);

构造语句,这个type的写法和smali语法一样

1
2
var objectclass= Java.use("java.lang.Object");
var ConstructorParam =Java.array('Ljava.lang.Object;',[objectclass.class]);

然后调用getDeclaredConstructor方法获得构造器

1
var a = hookClassCast.getDeclaredConstructor(ConstructorParam);

这样通过指定构造函数的参数,返回了一个指定的构造函数对象,Constructor对象中有一个方法newInstance(Object ... initargs),这里的initargs即为要传给构造函数的参数。使用newInstance就获取了MainActivityPatch类的实例化对象

1
2
3
var orininclass = Java.use("cn.chaitin.geektan.crackme.MainActivity");
var mainAc = orininclass.$new();
var instance = Constructor.newInstance([mainAc]);

调用实例化对象的方法

得到实例化的对象后,就可以通过getDeclaredMethods方法获取所有这个类的类方法,查看getDeclaredMethods的原型

1
2
3
public Method[] getDeclaredMethods() throws SecurityException {
......
}

通过原型可以知道该方法返回的是Method数组,Joseph函数在数组中第一个。

1
2
var func = hookClassCast.getDeclaredMethods();
var f = func[0];

知道目标函数的位置后,就可以使用Method.invoke方法调用该方法,看下invoke方法的参数,第一个参数是执行这个方法的对象实例,第二个参数是带入的实际值数组

1
public native Object invoke(Object obj, Object... args)

所以执行Joseph(5,6)的js语句是

1
2
3
4
5
var Integerclass = Java.use("java.lang.Integer");
var num1 = Integerclass.$new(5);
var num2 = Integerclass.$new(6);
var numArr1 = Java.array('Ljava.lang.Object;',[num1,num2]);
var rtn1 = f.invoke(instance,numArr1);

最终的EXP如下(参考看雪论坛ghostmazeW

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
Java.perform(function(){
var hookClass = undefined;
var ClassUse = Java.use("java.lang.Class");
var objectclass= Java.use("java.lang.Object");
var dexclassLoader = Java.use("dalvik.system.DexClassLoader");
var orininclass = Java.use("cn.chaitin.geektan.crackme.MainActivity");
var Integerclass = Java.use("java.lang.Integer");
var mainAc = orininclass.$new();


dexclassLoader.loadClass.overload('java.lang.String').implementation = function(name){
var hookname = "cn.chaitin.geektan.crackme.MainActivityPatch";
var result = this.loadClass(name,false);
if(name == hookname){
var hookClass = result;
var hookClassCast = Java.cast(hookClass,ClassUse);
console.log("-----------------------------GET Constructor-------------------------------------");
var ConstructorParam =Java.array('Ljava.lang.Object;',[objectclass.class]);
var Constructor = hookClassCast.getDeclaredConstructor(ConstructorParam);
console.log("Constructor:"+Constructor);
console.log("orinin:"+mainAc);
var instance = Constructor.newInstance([mainAc]);
console.log("patchAc:"+instance);
send(instance);

console.log("-----------------------------GET Methods----------------------------");
var func = hookClassCast.getDeclaredMethods();
console.log(func);
console.log("--------------------------GET Joseph Function---------------------------");
console.log(func[0]);
var f = func[0];
var num1 = Integerclass.$new(5);
var num2 = Integerclass.$new(6);
var numArr1 = Java.array('Ljava.lang.Object;',[num1,num2]);
var num3 = Integerclass.$new(7);
var num4 = Integerclass.$new(8);
var numArr2 = Java.array('Ljava.lang.Object;',[num3,num4]);
console.log("-----------------------------GET Array------------------------------");
console.log(numArr1);
console.log(numArr2);
var rtn1 = f.invoke(instance,numArr1);
var rtn2 = f.invoke(instance,numArr2);
console.log("--------------------------------FLAG---------------------------------");
console.log("DDCTF{"+rtn1+rtn2+"}");
console.log("--------------------------------OVER--------------------------------");
return result;

}
return result;
}
});

经测这个方法在Genymotion模拟器的android4.4和androdi6.0均测试失败…Hook不到loadClass方法。