Android程序分析入门 – 作者:tales

前言

分析Android程序是开发Android程序的逆过程,要想分析一个Android程序,首先应该了解其开发流程、程序结构、语句分支和解密原理等。

一、编写Android程序

使用Android studio编写Android应用程序
15856456208149.jpg

加载完成进入主界面后,点击activity_main.xml进入工程布局界面,然后可以拖动左边的palette内的各种各样的控件,系统就会自动帮我们生成xml代码。
15856459277008.jpg也可以在工程布局文件activity_main.xml中,手动添加或调整输入框的代码信息。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="@string/info"
        android:textSize="20sp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        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:hint="@string/hint_username"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:layout_weight="1"
            android:ems="10" >
        </EditText>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/sn" />
        <EditText
            android:id="@+id/edit_sn"
            android:hint="@string/hint_sn"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:layout_weight="1"
            android:ems="10" >
        </EditText>
    </LinearLayout>

    <Button
        android:id="@+id/button_register"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="10dp"
        android:layout_gravity="right"
        android:text="@string/register" />

</LinearLayout>

开发Android程序时,values目录下的strings.xml文件存放的字符串会在gen//R.java文件的string类中所标识。

<resources>
    <string name="app_name">TideCrackme</string>
    <string name="menu_settings">Settings</string>
    <string name="title_activity_main">Crackme0201</string>
    <string name="info">Tide-Android演示</string>
    <string name="username">用户名:</string>
    <string name="sn">注册码:</string>
    <string name="register">注    册</string>
    <string name="hint_username">请输入用户名</string>
    <string name="hint_sn">请输入注册码</string>
    <string name="unregister">程序未注册</string>
    <string name="registered">程序已注册</string>
    <string name="unsuccessed">无效用户名或注册码</string>
    <string name="successed">恭喜您!注册成功</string>
</resources>

编写MainActivity类的代码
打开MainActivity.java文件,如下

package com.example.textdemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

   }
}

然后我们在其添加两个方法。
checkcode() 该方法主要用于计算用户名和注册码是否匹配;
OnCreate() 该方法主要用于添加注册按钮点击事件的监听器。

package com.droider.tideandroid;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import android.support.v7.app.AppCompatActivity;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MainActivity extends AppCompatActivity {
    private EditText edit_userName;
    private EditText edit_sn;
    private Button btn_register;

    @Override
    public 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_sn);
        btn_register = (Button) findViewById(R.id.button_register);
        btn_register.setOnClickListener(new OnClickListener() {

            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.registered);
                }
            }
        });
    }

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

    private static String toHexString(byte[] bytes, String separator) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xFF & b);
            if(hex.length() == 1){
                hexString.append('0');
            }
            hexString.append(hex).append(separator);
        }
        return hexString.toString();
    }
}

checkcode()方法
先利用md5算法计算出用户名字符串的散列值,然后将计算结果转换为长度为32位的16进制字符串,然后将字符串中的所有奇数位进行重新组合生成新的字符串,该字符串就是最终的注册码。最后将该字符串与传入的注册码进行比较,如果二者相同,表示注册码是正确的;两者不同,表示注册码为错误。
OnCreate()方法
如果用户名与注册码匹配,就弹出注册成功的提示;如果用户名与注册码不匹配,则弹出用户名与注册码无效的提示。

MainActivity.java与activity_main.xml建立联系

Android手机运行java程序时,MainActivity.java内的setContentView(R.layout.activity_main);这段代码告诉我们:启动一个内容视图为activity_main.xml文件,就会去找activity_main.xml文件,然后Android系统会转换成各种各样的控件,这时候程序上就显示activity_main.xml内所写好的控件了。

二、破解Android程序

破解android程序的流程:
1、分析APP中的错误提示
2、反编译APK文件,生成smali格式的反汇编代码
3、阅读smali文件的代码理解程序运行机制,找到如破口,并对代码进行修改
4、重打包并签名
5、运行测试

1、反编译apk

使用android killer反编译apk
smali目录存放了程序的所有反汇编代码;
res目录中存放的则是程序中的所有资源文件;
15852752239104.jpg

2、分析APP中的错误提示

寻找突破口是分析android程序的关键。一般的做法是按程序中的错误提示信息来找到关键代码,因为错误提示代码附近通常就是程序的核心验证代码,可以通过阅读这些代码来理解程序的流程。
15852757467790.jpg一般错误提示属于android程序中的字符串资源,在开发android程序时,这些字符串会被硬编码到源码中,一般引用自res\values下的strings.xms文件。
通过反编译可成功搜索到该错误提示。
15852760476942.jpg反编译APK之后,所有的索引值都保存在了public.xml文件中,“无效用户名或注册码”的字符串名称为”unsuccessed”

R.java文件介绍:gen目录下的R.java文件是编译器自动生成的,它无需开发人员对其进行维护。R.java会自动收录当前应用中所有的资源,并根据这些资源建立对应的ID,包括:布局资源、控件资源、String资源、Drawable资源等。我们可以简单的把R.java理解成是当前Android应用的资源字典。

15852777497735.jpgunsuccessed字符串的id值为0x7f0b002c,然后搜索含有”0x7f0b002c”字符串文件,如下:

15852778351231.jpgpublic.xml和R$string.smali为程序的资源文件,
MainActivity$1.smali文件进行了调用:
15852784651863.jpg发现line34处调用了checkcode()方法进行注册码合法性检测
checkecode()方法返回布尔类型的值。

move-result v0  //将返回的结果保存到v0寄存器
const/4 v1, 0x0   //4字节常量  v1=0
if-nez v0, :cond_0  //对v0寄存器进行判断,如果其值不为0(条件为真)就跳转到cond_0标号出,反之继续往下执行

如果代码不跳转,会执行如下代码:
15852796280029.jpg

const v2, 0x7f0b002c 
//将v2寄存器传入 unsuccessed字符串的id值"0x7f0b002c"
invoke-static {v0, v2, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast;    
//调用Toast;->makeText()方法创建字符串,
.line 37 invoke-virtual {v0}, Landroid/widget/Toast;->show()V   
//显示结果

如果代码跳转,会执行如下代码:
15852845549249.jpg会弹出注册成功的提示,这里的跳转成功意味着程序注册成功。

3、修改smali文件代码

通过分析得知,if-nez v0, :cond_0是程序破解的关键点。

if-nez是Dalvik指令集中的一个条件跳转指令,与之类似的指令有if-eqz、if-gez、if-lez等。
与if-nez指令功能相反的指令为if-eqz 表示比较结果为0或者相等时跳转。

打开MainActivity$1.smali文件,修改if-nez v0, :cond_0为if-eqz v0, :cond_0 然后保存后退出即可修改成功。
15852850779332.jpg

4、重编译APK并签名

使用Android killer可一键进行文件重打包,并自动为该APK文件签名

三、安装测试

将打包好的apk文件,安装在手机或模拟器中,输入任意用户名和注册码,查看程序弹出的提示信息,发现会提示“程序已注册”,意味着破解成功。
15852858028240.jpg

四、总结

破解Android程序的大致流程为:
分析发现程序错误提示-反编译定位程序关键代码-修改代码-重打包签名-测试
本次只是对Android程序分析的基础过程进行了总结,但实际测试过程中,很多APP都进行加壳保护并对代码进行了混淆,使得分析起来相当困难复杂,需要使用动态调式与其它方法进行分析。

来源:freebuf.com 2020-04-13 16:54:51 by: tales

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论