• <<Android软件安全权威指南>>笔记1 第一个Android程序分析


    <<Android软件安全权威指南>>笔记

    第二章 如何分析Android程序

    编写一个Android程序

    新建一个Android工程,名称为Crackme0201

    主界面添加用户名和注册码输入框

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity"
        android:id="@+id/linearLayout">
    
        <TextView
            android:id="@+id/textView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/top_string"
            android:textAlignment="center"
            android:gravity="center_horizontal" />
    
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/username"/>
    
            <EditText
                android:id="@+id/edit_username"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:ems="10"
                android:layout_marginRight="10dp"
                android:layout_marginLeft="10dp"
                android:layout_weight="1"
                android:hint="@string/hint_username"/>
    
        </LinearLayout>
    
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/regCode"/>
    
            <EditText
                android:id="@+id/edit_regCode"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:ems="10"
                android:hint="@string/hint_regCode"
                android:layout_marginLeft="10dp"
                android:layout_marginRight="10dp"
                android:layout_weight="1" />
    
        </LinearLayout>
    
        <Button
            android:id="@+id/button_register"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/btn_register"
            android:layout_marginRight="10dp"
            android:layout_gravity="right"/>
    
    </LinearLayout>
    

    界面

    然后,编写MainActivity类的代码,添加一个checkSN()方法

        private boolean checkSN(String userName, String sn) {
            try {
                if ((userName == null) || (userName.length() == 0)) {
                    return false;
                }
    
                if ((sn == null) || (sn.length() != 16)) {
                    return false;
                }
    
                MessageDigest digest = MessageDigest.getInstance("MD5");
                digest.reset();
                digest.update(userName.getBytes());
                byte[] bytes = digest.digest();
                String hexstr = toHexString(bytes, "");
                StringBuilder stringBuilder = new StringBuilder();
                for (int i = 0; i < hexstr.length(); i+=2) {
                    stringBuilder.append(hexstr.charAt(i));
                }
                String userSN = stringBuilder.toString();
                //Log.d("crackme", hexstr);
                //Log.d("crackme", userSN);
    
                if (!userSN.equalsIgnoreCase(sn)) {
                    return false;
                }
    
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
                return false;
            }
            return true;
        }
    

    该方法计算用户名与注册码是否匹配,用MD5计算用户名的散列值,转换为hex串,128bit转换为32位十六进制串,再取32位中的奇数位,拼成16位,即为注册码。

    最后判断传入的注册码与计算得出的注册码是否匹配。

    MainActivity中的onCreate()方法中添加注册按钮点击事件

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            setTitle(R.string.unregister);
            edit_userName = (EditText) findViewById(R.id.edit_username);
            edit_sn = (EditText) findViewById(R.id.edit_regCode);
            btn_register = (Button) findViewById(R.id.button_register);
            btn_register.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (!checkSN(edit_userName.getText().toString().trim(),
                            edit_sn.getText().toString().trim())) {
                        Toast.makeText(MainActivity.this, R.string.unsuccessed,
                                Toast.LENGTH_SHORT).show();
                    } else {
                        Toast.makeText(MainActivity.this, R.string.successed,
                                Toast.LENGTH_SHORT).show();
                        btn_register.setEnabled(false);
                        setTitle(R.string.register);
                    }
                }
            });
    
        }
    

    如果匹配,则弹出注册成功,如果不匹配,则弹出注册无效的提示


    编译APK文件

    有两种编译方式,使用命令行和AS的Build菜单

    命令行

    gradlew是基于task来编译项目的,在命令行下执行gradlew tasks会列出所有支持的task,我们主要关注编译使用的Build tasks和安装APK时用的Install tasks

    生成Debug调试版本的APK:./gradlew assembleDebug,会在Crackme0201/app/build/outputs/apk目录下生成app-debug.apk文件

    安装Debug版本APK:./gradlew installDebug,会安装到与系统相连的设备/模拟器

    adb devices,列出当前连接的设备,包括模拟器

    AS编译

    直接点击run app会自动编译安装

    只编译不安装点make project


    破解第一个Android程序

    以Crackme0201为例

    通常方法是,使用ApkTool反编译APK文件,生成smali格式的反汇编代码,通过阅读smali代码理解程序运行机制,找到突破口进行修改,再使用ApkTool工具重新编译为APK并签名,进行测试

    上述过程循环,直到完全破解

    实际中还可以用IDA Pro直接分析APK文件,使用dex2jarjd-gui配合进行Java源码级分析

    反编译APK文件

    下载ApkTool工具,添加路径到PATH环境变量,命令行直接使用

    apktool d ./apk-debug.apk -o outdir
    

    会在outdir文件夹下生成反编译文件,smali目录下是程序的所有反汇编代码,res目录下是程序所有资源文件,子目录的文件结构与开发源码一致。

    分析APK文件

    找到突破口,一般是错误提示信息,属于字符串资源

    APK打包时,string.xml文件被加密存储为resources.arsc文件,反编译会解密

    string.xml中的所有字符串资源都在gen/R.java文件中的String类中标识,每个字符串都有int类型的索引值,对应关系在同目录下的public.xml文件中

    找到unsuccessed字符串和对应索引值

    索引

    但是搜索文件夹下没有与索引值相关的信息

    接下来生成Release版本APK文件

    用AS进行签名生成app-release.apk,再用apktool进行反编译

    找到unsuccessed对应的索引值,全局搜索,在MainActivity$1.smali中找到了索引值

    .line 33处调用checkSN()(但是这里是access$000,没有#备注,不知道为啥)

        .line 33
        invoke-static {p1, v0, v1}, Lorg/nuaa/crackme0201/MainActivity;->access$000(Lorg/nuaa/crackme0201/MainActivity;Ljava/lang/String;Ljava/lang/String;)Z
    
        move-result p1
    
        const/4 v0, 0x0
    
        if-nez p1, :cond_0
    
    

    意思是,返回Boolean类型值(哪里看出来的?)

    返回结果保存在p1

    move-result p1
    

    忽略掉第二行,对寄存器p1进行判断,其值不为0,即条件为真则跳转cond_0处

    const/4 v0, 0x0
    
    if-nez p1, :cond_0
    

    继续往下看,不跳转会执行

        .line 35
        iget-object p1, p0, Lorg/nuaa/crackme0201/MainActivity$1;->this$0:Lorg/nuaa/crackme0201/MainActivity;
    
        const v1, 0x7f0b0032
    
        invoke-static {p1, v1, v0}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast;
    
        move-result-object p1
    
        .line 36
        invoke-virtual {p1}, Landroid/widget/Toast;->show()V
    
        goto :goto_0
    

    .line 35处事要iget-object指令获取MainActivity的实例的引用,-this$0是内部类MainActivity$1中的一个synthetic字段,存储父类MainActivity的引用

    const v1, 0x7f0b0032
    

    该指令是将v1寄存器传入0x7f0b0032,也就是unsuccessed的id值,接下来调用Toast;->makeToast()

    如果跳转cond_0,则执行如下指令

        .line 38
        :cond_0
        iget-object p1, p0, Lorg/nuaa/crackme0201/MainActivity$1;->this$0:Lorg/nuaa/crackme0201/MainActivity;
    
        const v1, 0x7f0b002f
    
        invoke-static {p1, v1, v0}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast;
    
        move-result-object p1
    
        .line 39
        invoke-virtual {p1}, Landroid/widget/Toast;->show()V
    
        .line 40
        iget-object p1, p0, Lorg/nuaa/crackme0201/MainActivity$1;->this$0:Lorg/nuaa/crackme0201/MainActivity;
    
        iget-object p1, p1, Lorg/nuaa/crackme0201/MainActivity;->btn_register:Landroid/widget/Button;
    
        invoke-virtual {p1, v0}, Landroid/widget/Button;->setEnabled(Z)V
    
        .line 41
        iget-object p1, p0, Lorg/nuaa/crackme0201/MainActivity$1;->this$0:Lorg/nuaa/crackme0201/MainActivity;
    
        const v0, 0x7f0b002c
    
        invoke-virtual {p1, v0}, Lorg/nuaa/crackme0201/MainActivity;->setTitle(I)V
    
        :goto_0
        return-void
    

    这里的代码是注册成功时候的逻辑

    修改smali文件代码

    通过上述分析,,line 32处的代码,if-nez p1, :cond_0是跳转关键点

    这是Dalvik指令集的一个条件跳转指令(和汇编差不多),应该是not equal zero,不等于0的意思,相反的指令是if-eqz,表示等于0时跳转

    就用if-eqz代替if-nez,保存修改

    if-eqz p1, :cond_0
    
    重新编译APK并签名

    修改好以后,使用apktool重新编译打包

    apktool b outdir_rel
    

    outdir_rel/dist目录下生成apk文件

    要安装还需要签名,之前签名的时候生成了证书,我们可以用命令行来对apk进行签名

    apksigner sign --ks [xxx.jks] --out [xxx.apk] [signed.apk]
    

    apksigner是Android的build-tool中自带的,--ks后接数字证书存储路径,--out后接签名后apk输出路径,最后是原apk路径

    在Terminal下,模拟器开启的情况下,推出原应用,输入命令卸载

    adb uninstall org.nuaa.crackme0201
    

    再安装签名后的apk

    adb install signed.apk
    

    安装成功,测试随意的用户名+注册码都可以验证成功。

    最后随意验证


    总结

    一般流程

    反编译 -> 分析 -> 修改 -> 回编译 -> 签名

    集成工具:MAC下Android-Crack-Tool,Windows下Android Killer

  • 相关阅读:
    C# SQLiteHelper
    C# 自定义等待窗口
    C# Work PPT to PDF
    SQL 分隔字符串
    SQL 客户端查看
    SQL 自定义四舍五入
    SQL 并联更新
    C# 委托简单例子
    每天一个Linux命令(52)telnet命令
    每天一个Linux命令(51)ss命令
  • 原文地址:https://www.cnblogs.com/burymyname/p/13121689.html
Copyright © 2020-2023  润新知