最近分析某一apk,被其中的各种try-catch-finally块虐得死去活来,有木有。。。
由于喜欢刨根,各种谷歌都没有给出解释。 一番研究之后,发现确实有些蹊跷。(本人水平有限,不喜勿喷,谢谢
)
切入正题:
try{}catch反编译后,格式:
eg: .catch Ljava/io/FileNotFoundException; {:try_start_0 .. :try_end_0} :catch_0
加上finally,那就多一行声明:
eg: .catchall {:try_start_0 .. :try_end_0} :catchall_0
大家都知道,finally块中的代码始终都要执行,那么是不是类似goto到finally块中去执行代码呢? 可惜不是,是直接复制了
3份。
比如:
try {
FileInputStream stream = new FileInputStream(new File("/mnt/sdcard/test.txt"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
System.out.println("Fin");
}
smali:
:try_start_0
new-instance v1, Ljava/io/FileInputStream;
new-instance v2, Ljava/io/File;
const-string v3, "/mnt/sdcard/test.txt"
invoke-direct {v2, v3}, Ljava/io/File;-><init>(Ljava/lang/String;)V
invoke-direct {v1, v2}, Ljava/io/FileInputStream;-><init>(Ljava/io/File;)V
:try_end_0
.catchall {:try_start_0 .. :try_end_0} :catchall_0
.catch Ljava/io/FileNotFoundException; {:try_start_0 .. :try_end_0} :catch_0
.line 28
sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v2, "Fin"
invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
.line 30
:goto_0
return-void
.line 24
:catch_0
move-exception v0
.line 26
.local v0, e:Ljava/io/FileNotFoundException;
:try_start_1
invoke-virtual {v0}, Ljava/io/FileNotFoundException;->printStackTrace()V
:try_end_1
.catchall {:try_start_1 .. :try_end_1} :catchall_0
.line 28
sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v2, "Fin"
invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
goto :goto_0
.line 27
.end local v0 #e:Ljava/io/FileNotFoundException;
:catchall_0
move-exception v1
.line 28
sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v3, "Fin"
invoke-virtual {v2, v3}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
.line 29
throw v1
(由于黄色看不清,这里修改为红色)
不难发现,正常执行流程和catch块中增加了finally中的代码,而且二者是同一个出口。(goto_0)
那黄色(第三块红色)那块是什么???
经过多次测试,只要catch块中调用了函数,就会隐式生成一个try,且只有.catchall
:try_start_1
invoke-virtual {v0}, Ljava/io/FileNotFoundException;->printStackTrace()V
:try_end_1
.catchall {:try_start_1 .. :try_end_1} :catchall_0
catchall_n一般结构是:
move-exception vm
......
throw vm
另外,经过多次动态调试smali,都没有出现程序进入.catchall_n模块。估计是没有构造出相应的触发条件。由于运行时异常不需要显式声明,猜测可能运行时异常触发。故构造一下代码:
public void fun(int flag) throws IllegalArgumentException
{
switch(flag){
case 1: throw new IllegalArgumentException();
case 2: throw new RuntimeException();
default: break;
}
}
public void Test(int flag)
{
try {
fun(flag);
foo();
} catch (Exception e) {
foo();
}finally{
Log.i("Finally", "Exit");
}
}
public void foo(){
throw new RuntimeException();
}
由于Log.i("Finally", "Exit");会被拷贝三份,故修改.catall中的输出,进行区别。具体如下:
:catchall_1a
move-exception v1
.line 39
const-string v2, "Finally"
const-string v3, "Exit_Finally"
修改"Exit"为"Exit_Finally"
invoke-static {v2, v3}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
.line 40
throw v1
onCreate分别测试调用Test(0)和Test(1),均打印出"Exit_Finally",并未打印出"Exit"。
这下终于明白了.catchall的作用,确实保证了finally块中的代码一定会被执行。
PS:要是多重try-catch-finally嵌套,而且finally块中有很多代码,那就。。。 万恶的finally块,虐了我千百遍。。。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)