概述
本文主要讲述Andriod中java层的逆向分析,这里我们只讨论简单的一些基本技术,对加固或代码混淆在以后涉及。
实验目的
通过本文来了解apk的反编译和回编译,Smali汇编中代码插桩,绕过java层检测进行登录。
前置条件
阅读本文你需要掌握以下知识:
- Android的基本开发
- Smali汇编基础
Smali手册:http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html
实验环境
Android4.0.3(x86) 虚拟机(这里我们暂时不对so层进行分析,所以不在乎CPU是哪个平台)
apktool.jar
Apk上上签
cm.apk (这个是自己编写的一个简单的demo程序读者也可以自己编写)
链接:https://pan.baidu.com/s/1jI8_bxmTaGL4ESO2JdNEYQ
提取码:ya9j
cm程序体验
使用adb命令安装程序进入虚拟机
adb install cm.apk
运行后如下图:
image.png
输入密码点击确定,正确的话提示正确,否则提示错误。我们的目的就是去绕过这个检测。
反编译
使用以下命令反编译apk:
apktool.jar d cm.apk
d后面跟上你的apk,命令执行完之后目录下出现和apk同名文件夹,进入结构如下:
image.png
如果缺少太多文件请更换apktool版本
-
目录介绍
original:签名信息
res:资源文件,我们在xml定义的字符串,样式也在里面
smali:java代码反编译后得到的smali代码
AndroidManifest.xml:清单文件
apktool.yml:apktool生成的 -
smali代码
进入smali文件夹里面有两个文件夹
android:系统生成的smali代码
com:这个就对应了android项目中创建的包名(我们编写的业务代码逻辑都在这里面),所以我们主要对它进行分析 -
清单文件
<?xml version="1.0" encoding="utf-8" standalone="no"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.test1">
<application android:allowBackup="true" android:debuggable="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme">
<activity android:label="@string/app_name" android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
从清单文件中可以知道我们分析的app中只有一个activity就是MainActivity,它也是入口activity,所有我们直接进到smali\com\example\test1\MainActivity.smali
(这里注意MainActivity$1.smali是MainActivity类中的匿名类也会生成为一个单独的smali文件后面的数字随着匿名内部类的数量依次递增)
MainActivity.smali(里面内容过多我这里只截取关键代码)
# direct methods
.method public constructor <init>()V
.locals 1
.prologue
const/4 v0, 0x0
.line 15
invoke-direct {p0}, Landroid/support/v7/app/ActionBarActivity;-><init>()V
.line 20
iput-object v0, p0, Lcom/example/test1/MainActivity;->pswd1:Ljava/lang/String;
.line 21
iput-object v0, p0, Lcom/example/test1/MainActivity;->result1:Ljava/lang/String;
.line 22
iput-object v0, p0, Lcom/example/test1/MainActivity;->result2:Ljava/lang/String;
#new MainActivity$1 这个匿名内部类就是onclick类的实例对象
.line 57
new-instance v0, Lcom/example/test1/MainActivity$1;
invoke-direct {v0, p0}, Lcom/example/test1/MainActivity$1;-><init>(Lcom/example/test1/MainActivity;)V
# 放入到this中
iput-object v0, p0, Lcom/example/test1/MainActivity;->listener:Landroid/view/View$OnClickListener;
.line 15
return-void
.end method
# virtual methods
.method protected onCreate(Landroid/os/Bundle;)V
.locals 4
.param p1, "savedInstanceState" # Landroid/os/Bundle;
.prologue
.line 26
invoke-super {p0, p1}, Landroid/support/v7/app/ActionBarActivity;->onCreate(Landroid/os/Bundle;)V
.line 27
const v2, 0x7f030018
invoke-virtual {p0, v2}, Lcom/example/test1/MainActivity;->setContentView(I)V
.line 28
const v2, 0x7f05003d
invoke-virtual {p0, v2}, Lcom/example/test1/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v2
check-cast v2, Landroid/widget/EditText;
iput-object v2, p0, Lcom/example/test1/MainActivity;->Pswd:Landroid/widget/EditText;
.line 29
const v2, 0x7f05003e
invoke-virtual {p0, v2}, Lcom/example/test1/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v2
check-cast v2, Landroid/widget/Button;
iput-object v2, p0, Lcom/example/test1/MainActivity;->bt:Landroid/widget/Button;
#将button放入到v2
.line 30
iget-object v2, p0, Lcom/example/test1/MainActivity;->bt:Landroid/widget/Button;
#将listener放入到v3
iget-object v3, p0, Lcom/example/test1/MainActivity;->listener:Landroid/view/View$OnClickListener;
#设置点击事件
invoke-virtual {v2, v3}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V
.line 31
const-string v0, "12345678"
.line 32
.local v0, "key":Ljava/lang/String;
iget-object v1, p0, Lcom/example/test1/MainActivity;->pswd1:Ljava/lang/String;
.line 34
.local v1, "text":Ljava/lang/String;
#插桩代码 弹出Toast 打印Shark Chilli Log
const-string v0, "Shark Chilli Log"
const/4 v1, 0x1
invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v0
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
return-void
.end method
我们在OnCreate方法最后面加上了我们的插桩代码~
关键代码做了注释,从上面可以看出我们的关键逻辑在MainActivity$1这个OnClickListener匿名内部类里面的Onclick函数:
MainActivity$1.smali
# virtual methods
.method public onClick(Landroid/view/View;)V
.locals 5
.param p1, "v" # Landroid/view/View;
.prologue
const/4 v4, 0x1
.line 60
invoke-virtual {p1}, Landroid/view/View;->getId()I
move-result v1
packed-switch v1, :pswitch_data_0
.line 88
:goto_0
return-void
.line 65
:pswitch_0
iget-object v1, p0, Lcom/example/test1/MainActivity$1;->this$0:Lcom/example/test1/MainActivity;
iget-object v2, p0, Lcom/example/test1/MainActivity$1;->this$0:Lcom/example/test1/MainActivity;
invoke-static {v2}, Lcom/example/test1/MainActivity;->access$0(Lcom/example/test1/MainActivity;)Landroid/widget/EditText;
move-result-object v2
invoke-virtual {v2}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
move-result-object v2
invoke-interface {v2}, Landroid/text/Editable;->toString()Ljava/lang/String;
move-result-object v2
invoke-static {v1, v2}, Lcom/example/test1/MainActivity;->access$1(Lcom/example/test1/MainActivity;Ljava/lang/String;)V
.line 68
:try_start_0
iget-object v1, p0, Lcom/example/test1/MainActivity$1;->this$0:Lcom/example/test1/MainActivity;
iget-object v2, p0, Lcom/example/test1/MainActivity$1;->this$0:Lcom/example/test1/MainActivity;
invoke-static {v2}, Lcom/example/test1/MainActivity;->access$2(Lcom/example/test1/MainActivity;)Ljava/lang/String;
move-result-object v2
const-string v3, "poi7y6gt"
#DES加密 将输入的密码加密
invoke-static {v2, v3}, Lcom/example/test1/DES;->encryptDES(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
move-result-object v2
# access$3里面就一句 iput-object p1, p0, Lcom/example/test1/MainActivity;->result1:Ljava/lang/String;
invoke-static {v1, v2}, Lcom/example/test1/MainActivity;->access$3(Lcom/example/test1/MainActivity;Ljava/lang/String;)V
:try_end_0
.catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
.line 76
:goto_1
#this$0就是MainActivity的实例对象
iget-object v1, p0, Lcom/example/test1/MainActivity$1;->this$0:Lcom/example/test1/MainActivity;
#调用MainActivity;->access$4方法 invoke-direct {p0}, Lcom/example/test1/MainActivity;->check()Z
invoke-static {v1}, Lcom/example/test1/MainActivity;->access$4(Lcom/example/test1/MainActivity;)Z
#结果放入v1
move-result v1
#等于0则跳转到 我们改成if-nez不等于0则跳转就所有密码都能过去了
if-eqz v1, :cond_0
.line 78
iget-object v1, p0, Lcom/example/test1/MainActivity$1;->this$0:Lcom/example/test1/MainActivity;
const-string v2, "\u606d\u559c\u8fc7\u5173"
invoke-static {v1, v2, v4}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v1
invoke-virtual {v1}, Landroid/widget/Toast;->show()V
goto :goto_0
.line 70
:catch_0
move-exception v0
.line 72
.local v0, "e":Ljava/lang/Exception;
invoke-virtual {v0}, Ljava/lang/Exception;->printStackTrace()V
goto :goto_1
.line 82
.end local v0 # "e":Ljava/lang/Exception;
:cond_0
iget-object v1, p0, Lcom/example/test1/MainActivity$1;->this$0:Lcom/example/test1/MainActivity;
const-string v2, "\u5bc6\u7801\u9519\u8bef"
invoke-static {v1, v2, v4}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v1
invoke-virtual {v1}, Landroid/widget/Toast;->show()V
goto :goto_0
.line 60
:pswitch_data_0
.packed-switch 0x7f05003e
:pswitch_0
.end packed-switch
.end method
在上面修改了if-eqz v1, :cond_0为 if-nez v1, :cond_0就可以绕过检测了,我们可以看到起加密算法的主要逻辑是在Lcom/example/test1/MainActivity;->check()Z中。并且这个应该是一个DES加密,我们暂时不对加密算法讨论。
修改代码后,进行回编译
apktool.jar b cm
在cm目录中会出现dist目录,里面就是我们回编译的apk
image.png
使用上上签进行签名(你也可以使用jar文件签名,这里为了方便使用了上上签):
签名后生成了cm_Signed.apk
image.png
丢进android中运行
adb install cm_Signed.apk
运行后进入后:
image.png填写任意密码效果如下:
image.png
尾言
到此为止我们就成功的绕过了密码验证,这里的加密方法是在java中所以轻松的绕过它,但是大部分情况加密可能写在so层,所以我们需要进入到so层对代码分析。
接下来几天我将结合静态分析与动态调试,分析一个so层加密的Android应用.
网友评论