前言
分析Android程序是开发Android程序的逆过程,要想分析一个Android程序,首先应该了解其开发流程、程序结构、语句分支和解密原理等。
一、编写Android程序
使用Android studio编写Android应用程序
加载完成进入主界面后,点击activity_main.xml进入工程布局界面,然后可以拖动左边的palette内的各种各样的控件,系统就会自动帮我们生成xml代码。
也可以在工程布局文件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目录中存放的则是程序中的所有资源文件;
2、分析APP中的错误提示
寻找突破口是分析android程序的关键。一般的做法是按程序中的错误提示信息来找到关键代码,因为错误提示代码附近通常就是程序的核心验证代码,可以通过阅读这些代码来理解程序的流程。
一般错误提示属于android程序中的字符串资源,在开发android程序时,这些字符串会被硬编码到源码中,一般引用自res\values下的strings.xms文件。
通过反编译可成功搜索到该错误提示。
反编译APK之后,所有的索引值都保存在了public.xml文件中,“无效用户名或注册码”的字符串名称为”unsuccessed”
R.java文件介绍:gen目录下的R.java文件是编译器自动生成的,它无需开发人员对其进行维护。R.java会自动收录当前应用中所有的资源,并根据这些资源建立对应的ID,包括:布局资源、控件资源、String资源、Drawable资源等。我们可以简单的把R.java理解成是当前Android应用的资源字典。
unsuccessed字符串的id值为0x7f0b002c,然后搜索含有”0x7f0b002c”字符串文件,如下:
public.xml和R$string.smali
为程序的资源文件,MainActivity$1.smali
文件进行了调用:
发现line34处调用了checkcode()方法进行注册码合法性检测
checkecode()方法返回布尔类型的值。
move-result v0 //将返回的结果保存到v0寄存器 const/4 v1, 0x0 //4字节常量 v1=0 if-nez v0, :cond_0 //对v0寄存器进行判断,如果其值不为0(条件为真)就跳转到cond_0标号出,反之继续往下执行
如果代码不跳转,会执行如下代码:
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 //显示结果
如果代码跳转,会执行如下代码:
会弹出注册成功的提示,这里的跳转成功意味着程序注册成功。
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 然后保存后退出即可修改成功。
4、重编译APK并签名
使用Android killer可一键进行文件重打包,并自动为该APK文件签名
三、安装测试
将打包好的apk文件,安装在手机或模拟器中,输入任意用户名和注册码,查看程序弹出的提示信息,发现会提示“程序已注册”,意味着破解成功。
四、总结
破解Android程序的大致流程为:
分析发现程序错误提示-反编译–定位程序关键代码-修改代码-重打包签名-测试
本次只是对Android程序分析的基础过程进行了总结,但实际测试过程中,很多APP都进行加壳保护并对代码进行了混淆,使得分析起来相当困难复杂,需要使用动态调式与其它方法进行分析。
来源:freebuf.com 2020-04-13 16:54:51 by: tales
请登录后发表评论
注册