1. 内部类

内部类反编出来会出现如下结果:

内部类反编结果.png

1
2
3
4
5
6
7
//点开任意一个可以看出是实现的Runnable方法
public final class -$$Lambda$ExtNetworkTracker$InterfaceObserver$6h5H0nN5nrJkfCZwlwJqwAaVTc0 implements Runnable {
public final void run() {
// 调用的外部类的方法
InterfaceObserver.lambda$interfaceRemoved$1(this.f$0, this.f$1);
}
}

2. 给外部类参数赋值

如果在反编译文件中出现access$xxx等标识,基本都是对外部类变量赋值,找到外部类对应变量进行复制即可即可。

给外部类参数赋值.png

1
2
3
//以上两句话,相当于下面两句话,mLinkStateIfaceName,mNetworkLinkState是外部类的两个变量
mLinkStateIfaceName = ifaceName;
mNetworkLinkState = state;

3. Log日志

日常中打的log,在反编译之后,会使用StringBuilder来包装一句log日志。

log日志.png

1
2
//以上语音相当于一下语句
Log.d(TAG, "interfaceLinkStateChanged:" + ifaceName + " up: " + state);

4.for循环的解析

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
//如果遇到for (;;),其实就while循环遍历
for (;;){
bool1 = bool2;
//重试三次计数
if ((i >= 3) || (paramBoolean)) {}
try{
//实际逻辑
}
catch (Exception localException){
for (;;) {}
}
str.printStackTrace();
i += 1;
}

//解析代码
while (true) {
if (i >= 3) {
break;
}
try {
//实际逻辑
break;
} catch (Exception e) {
e.printStackTrace();
i++;
}
}

5.同步代码块

5.1 类锁

如果遇到try-finally组合,且finally是空的,考虑是synchronized代码同步块

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
//反编译代码
private void updateInterface(String paramString, boolean paramBoolean) {
try {
//实际逻辑代码
Object localObject = TAG;
StringBuilder localStringBuilder = new StringBuilder();
localStringBuilder.append("updateInterface(iface:");
localStringBuilder.append(paramString);
localStringBuilder.append(", status:");
localStringBuilder.append(paramBoolean);
localStringBuilder.append(")");
Log.d((String)localObject, localStringBuilder.toString());
return;
} finally {
}
}

//真实代码
private void updateInterface(String paramString, boolean paramBoolean) {
synchronized (this) {
//实际逻辑代码
Object localObject = TAG;
StringBuilder localStringBuilder = new StringBuilder();
localStringBuilder.append("updateInterface(iface:");
localStringBuilder.append(paramString);
localStringBuilder.append(", status:");
localStringBuilder.append(paramBoolean);
localStringBuilder.append(")");
Log.d((String)localObject, localStringBuilder.toString());
}
}

5.2 对象锁

1
2
3
4
//这个还是很容易看出是对象锁的,我之前有一处很难发现的代码没找到了,下次找到更新在这里,此处预留
synchronized (CarlibTimeoutUtil.this.mLock){
//真实逻辑
}

6.编译器对象擦除机制

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
//随意丢一个方法过来,分析java编译中对象擦除机制。为了方便好看,下面实例代码中使用“方法”来代替实际的方法。
private void updateInterface(String paramString, boolean paramBoolean) {
try {
Object localObject = TAG;
StringBuilder localStringBuilder = new StringBuilder();
localStringBuilder.append("updateInterface(iface:");
localStringBuilder.append(paramString);
localStringBuilder.append(", status:");
localStringBuilder.append(paramBoolean);
localStringBuilder.append(")");
Log.d((String) localObject, localStringBuilder.toString());
localObject = (ExtNetworkSet) this.mExtNetworkSets.方法;
if (localObject == null) {
localObject = TAG;
localStringBuilder = new StringBuilder();
localStringBuilder.append("ExtNetworkSet of ");
localStringBuilder.append(paramString);
localStringBuilder.append(" not found");
Log.e((String) localObject, localStringBuilder.toString());
return;
}
((ExtNetworkSet) localObject).mNetworkFactory.方法;
(((ExtNetworkSet) localObject).mIpConfig.方法;
((ExtNetworkSet) localObject).mNetworkFactory.方法;
((ExtNetworkSet) localObject).mNetworkFactory.方法;
return;
} finally {
}
}

从代码中可以看到localObject这个对象从方法进入到方法结束都一直使用的这个变量。这就是java在编译之后进行对象擦拭机制。进入方法之后,编译器会把所有对象擦拭成Object对象,然后在适合的位置强转成真实的对象。

例如:在方法进入,编译器先创建了一个Object对象,接收TAG参数,在Log.d方法中将它转化成String对象;
然后接收从mExtNetworkSets集合中获得的ExtNetworkSet对象,在方法最后强转成ExtNetworkSet对象来使用它。

7.关于资源文件的解析

在代码中有关于资源文件的应用如下
资源文件解析.png

解析方法:

  • 首先将十进制“2130837504”转化成十六进制为“0x7f020000”
  • 将apk文件直接拖入Android Studio,找到resources.arsc索引文件,然后根据要查看的内容类型找到具体的内容。

对应资源内容.png

8.超高难度解析–内部类调用外部类方法

内部类超高难度理解.png

该文件是内部类中的一个实现,实现了Runnable接口。

然后看InterfaceObserver.lambda$interfaceRemoved$1(this.f$0, this.f$1); 这个是interfaceRemoved接口方法中会调用$1(this.f$0, this.f$1)方法。

以上方法的破解有两个方案:

  • 一般是内部的实现方法,根据接口的名字interfaceRemoved猜测外部类private的哪个方法比较靠近这个实现,然后用这个方法去试逻辑是否吻合
  • 用排除法,先将其他方法都处理完成之后,看那几个方法没有被调用过,然后结合传入参数,实现内容就可以初步断定调用哪个方法。最后在编译出来上机验证是否符合猜测。

9.超高难度解析–解析协议

协议解析.png

如果遇到以上这种情况,反编译代码中会有奇形怪状的数字(hash值),这个情况一般是编译器为了找到运行时的对象,算了一个hash值,所以我们不必关心这个hash值,我们就看判断的内容即可。

根据截图以上的代码上下文,可以判断localObject2是协议的key值,localObject3是协议的value值,然后判断key值是否与对应的key值相等,然后处理对应的value值。

根据上面分析,解析结果可以判断为:

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
String key = pair[0];
String value = pair[1];
switch (key) {
case "ip":
ipConfig.ipAddress = new LinkAddress(value);
break;
case "domains":
ipConfig.domains = value;
break;
case "gateway":
ipConfig.gateway = InetAddress.parseNumericAddress(value);
break;
case "dns": {
ArrayList<InetAddress> dnsAddresses = new ArrayList<>();
for (String address : value.split(",")) {
dnsAddresses.add(InetAddress.parseNumericAddress(address));
}
ipConfig.dnsServers.addAll(dnsAddresses);
break;
}
default: {
throw new IllegalArgumentException("Unexpected key: " + key
+ " in " + configs);
}
}