1. 引入
当我们拿到一个APK,没有源代码,该怎么样去研究APK的核心逻辑呢?
限于运行环境的复杂,我们会首先使用静态分析的方式,大概可以想出这样一些静态分析APK的方法:
-
用apktool直接将APK转换为smali程序,再阅读smali代码(比较痛苦)
-
用dextojar将APK中的DEX转换为jar,再用JD-JUI来查看其java代码
-
用JEB,直接查看java或smali(JEB是收费软件,比较贵)
当我们做这样的静态分析,定位到某些关键逻辑,就要用动态调试的手段来观测某些变量的值了。
这时候,我们就会在APK中,关键逻辑的地方,插入LOG,然后运行观测,这个过程就是本文要讲述的内容。
本文的实验环境如下:
- windows 10
- apktool_2.4.0.jar
- java version “1.8.0_201”
2. 要分析的APK
我们自己写了一个很简单的APK(文件名为hello-apk.apk),后面称之为hello-apk,专门用于反编译分析。分析其他复杂APK的过程也和本文讲述的过程是一样的,只是用这个简单hello-apk,更能清晰、简洁、易懂的说明这个过程。
hello-apk的界面很简单,就是一个button,加上一个textview。当我们单击button,就会在textview上显示"Hello world! click by button!!"。其核心逻辑见下面代码
public class MainActivity extends AppCompatActivity {
private int count=0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void btn1ClickEvent(View target){
count++;
TextView txt=(TextView)findViewById(R.id.mytextview);//find output label by id
txt.setText("Hello world! click by button!!");
}
}
这里的关键逻辑,就是函数btn1ClickEvent()中的内容,它负责处理button的click事件。
接下来我们讲解如何查看count值的过程。
3. 安装配置apktool的步骤
-
具体步骤参考: https://ibotpeaches.github.io/Apktool/install/
-
将如下链接中显示的脚本内容保存为
apktool.bat
- https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/windows/apktool.bat
- 到如下链接下载最新版本的apktool
- https://bitbucket.org/iBotPeaches/apktool/downloads/
- 将下载的apktool重命名为
apktool.jar
,并与apktool.bat
放在同一个文件夹,添加环境变量
本文将hello-apk与apktool.jar
,apktool.bat
放在同一个文件夹,所以省去了添加环境变量的步骤。
4. 用apktool反编译APK为smali
使用如下命令(apktool d apkfile
)来反编译apk
>apktool d hello-apk.apk
解包执行结束后,我们在当前目录得到hello-apk文件夹,里面就是从APK中反编译出来的smali代码,资源文件,与manifest文件。
5. 插入调试用到的smali代码
在解包得到的smali文件夹中,我们可以找到需要添加LOG的smali文件,位于hello-apk/smali/com/example/ybdesire/hello/MainActivity.smali
。
关键函数btn1ClickEvent()的smali代码如下,可以对比上面的纯java代码,这就是java编译后的smali:
# virtual methods
.method public btn1ClickEvent(Landroid/view/View;)V
.locals 2#这个函数中,使用了2个局部变量,就是下面的v0,v1
.param p1, "target" # Landroid/view/View;# 函数形参
.line 19#将count变量++
iget v0, p0, Lcom/example/ybdesire/hello/MainActivity;->count:I
add-int/lit8 v0, v0, 0x1
iput v0, p0, Lcom/example/ybdesire/hello/MainActivity;->count:I
.line 20#找到textview的id
const v0, 0x7f070052
invoke-virtual {p0, v0}, Lcom/example/ybdesire/hello/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/TextView;
.line 21#将字符串显示到textview上
.local v0, "txt":Landroid/widget/TextView;
const-string v1, "Hello world! click by button!!"
invoke-virtual {v0, v1}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V
.line 23
return-void
.end method
我们想在上面程序的最后,插入一行java程序的smali代码,这样我们就能在程序运行时,通过log输出其局部变量的值,也就是能够动态查看中间结果了。
Log.d("btn_debug", "count="+count);//这是想插入的java程序
这一行java程序对应的smali程序如下:
.line 22
const-string v1, "btn_debug"
new-instance v2, Ljava/lang/StringBuilder;
invoke-direct {v2}, Ljava/lang/StringBuilder;-><init>()V
const-string v3, "count="
invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
# 通过阅读上面的smali,可以发现,count变量的值会被放到p0
iget v3, p0, Lcom/example/ybdesire/hello/MainActivity;->count:I
invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;
invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v2
invoke-static {v1, v2}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
接下来我们来修改smali:
-
将这段smali程序,插入到
hello-apk/smali/com/example/ybdesire/hello/MainActivity.smali
的.line 23
上面。 -
将
.locals 2
改为.locals 4
,因为上面插入的smali代码,还使用了v2与v3。
最终的smali如下:
# virtual methods
.method public btn1ClickEvent(Landroid/view/View;)V
.locals 4
.param p1, "target" # Landroid/view/View;
.line 19
iget v0, p0, Lcom/example/ybdesire/hello/MainActivity;->count:I
add-int/lit8 v0, v0, 0x1
iput v0, p0, Lcom/example/ybdesire/hello/MainActivity;->count:I
.line 20
const v0, 0x7f070052
invoke-virtual {p0, v0}, Lcom/example/ybdesire/hello/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/TextView;
.line 21
.local v0, "txt":Landroid/widget/TextView;
const-string v1, "Hello world! click by button!!"
invoke-virtual {v0, v1}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V
.line 22
const-string v1, "btn_debug"
new-instance v2, Ljava/lang/StringBuilder;
invoke-direct {v2}, Ljava/lang/StringBuilder;-><init>()V
const-string v3, "count="
invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
iget v3, p0, Lcom/example/ybdesire/hello/MainActivity;->count:I
invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;
invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v2
invoke-static {v1, v2}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
.line 23
return-void
.end method
通过这样修改smali,我们最终得到的程序逻辑,用java表示如下:
public void btn1ClickEvent(View target){
count++;
TextView txt=(TextView)findViewById(R.id.mytextview);//find output label by id
txt.setText("Hello world! click by button!!");
Log.d("btn_debug", "my count="+count);
}
6. 改动后的smali打包为APK
使用如下命令(apktool d dir_name
)来将改动后的smali,打包为APK文件
>apktool b hello-apk
打包完成后,可以在hello-apk/dist
中看到打包出来的APK文件。
7. 为APK签名
apktool打包出来的APK,缺少签名信息,无法在android设备安装运行,必须要重新签名(详细步骤参考[1])。
- 生成签名文件
运行如下命令
keytool -genkey -alias abc.keystore -keyalg RSA -validity 20000 -keystore abc.keystore
需要输入签名的密码与其他信息,下面用中文说明具体要输入的内容(括号中是说明,不是真实输入的内容):
hello-apk\dist>keytool -genkey -alias abc.keystore -keyalg RSA -validity 20000 -keystore abc.keystore
Enter keystore password:(输入密码)
Re-enter new password:(再次输入密码)
What is your first and last name?
[Unknown]: a
What is the name of your organizational unit?
[Unknown]: a
What is the name of your organization?
[Unknown]: a
What is the name of your City or Locality?
[Unknown]: a
What is the name of your State or Province?
[Unknown]: a
What is the two-letter country code for this unit?
[Unknown]: a
Is CN=a, OU=a, O=a, L=a, ST=a, C=a correct?
[no]: yes
Enter key password for <abc.keystore>
(RETURN if same as keystore password):(再次输入密码)
Re-enter new password:(再次输入密码)
Warning:
The JKS keystore uses a proprietary format. It is recommended to migrate to PKCS12 which is an industry standard format using "keytool -importkeystore -srckeystore abc.keystore -destkeystore abc.keystore -deststoretype pkcs12".
最终会生成一个abc.keystore证书文件。
- 为apk签名
运行如下命令,对hello-apk.apk
签名,签名后生成的文件为hello-apk-s.apk
。
jarsigner -verbose -keystore abc.keystore -signedjar hello-apk-s.apk hello-apk.apk abc.keystore
运行后,需要输入上面为签名设置的密码。
Enter Passphrase for keystore:
adding: META-INF/MANIFEST.MF
adding: META-INF/ABC_KEYS.SF
adding: META-INF/ABC_KEYS.RSA
signing: AndroidManifest.xml
signing: classes.dex
signing: res/anim/abc_fade_in.xml
signing: res/anim/abc_fade_out.xml
signing: res/anim/abc_grow_fade_in_from_bottom.xml
signing: resources.arsc
>>> Signer
X.509, CN=a, OU=a, O=a, L=a, ST=a, C=a
[trusted certificate]
jar signed.
Warning:
The signer's certificate is self-signed.
最终我们得到签名后的hello-apk-s.apk
文件。
8. 查看调试信息
签名后的hello-apk-s.apk
文件,可以直接安装在android设备上。
运行APP,用adb查看log,如下命令可以只查看tag为btn_debug的debug级别的log
adb logcat btn_debug:D
多次点击APP的button,可以得到如下log
09-01 16:33:41.863 1700 1700 I Zygote : Process 4887 exited due to signal (9)
09-01 16:33:42.057 3346 3346 D btn_debug: count=7
09-01 16:33:42.837 3346 3346 D btn_debug: count=8
09-01 16:33:42.993 3346 3346 D btn_debug: count=9
09-01 16:33:43.191 3346 3346 D btn_debug: count=10
这就实现了把APK中的局部变量count的值作为输出。
9. 总结
使用apktool,可以把APK解包,我们就能在解包后的smali中插入调试APK需要的代码,再将改动后的smali代码打包为APK,签名运行后,就能用adb看到我们插入smali代码的输出了。
10. 参考
- [1] https://blog.csdn.net/ybdesire/article/details/52505648