您的位置:首页 > 移动开发 > Android开发

Android反编译学习-使用APKtool破解注册类程序

2015-04-20 17:04 253 查看

#1 机制

破解
Android 程序通常的方法是将
apk 文件利用
ApkTool 反编译,生成
Smali 格式的反汇编代码,然后阅读
Smali
文件的代码来理解程序的运行机制,找到程序的突破口进行修改,最后使用
ApkTool 重新编译生成
apk 文件并签名,最后运行测试,如此循环,直至程序被成功破解。

#2 准备工作

APKtool的安装使用请参考笔者上篇博客
编写软件Crackme_01用做测试
value文件夹下的String.xml内容如下
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Crackme0201</string>
<string name="hello_world">Hello world!</string>
<string name="menu_settings">Settings</string>
<string name="title_activity_main">crackme02</string>
<string name="info">Android程序破解演示实例</string>
<string name="username">用户名: </string>
<string name="sn">注册码: </string>
<string name="register">注 册</string>
<string name="hint_username">请输入用户名</string>
<string name="hint_sn">请输入16位的注册码</string>
<string name="unregister">程序未注册</string>
<string name="registered">程序已注册</string>
<string name="unsuccessed">无效用户名或注册码</string>
<string name="successed">恭喜您!注册成功</string>
</resources>


onCreate方法修改如下
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); // 模拟程序已注册
}
}
});
}


MainActivity中添加类
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(); // 采用MD5对用户名进行Hash
String hexstr = bytes2HexString(bytes); // 将计算结果转化成字符串
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hexstr.length(); i += 2) {
sb.append(hexstr.charAt(i));
}
String userSN = sb.toString(); // 计算出的SN
if (!userSN.equalsIgnoreCase(sn)) // 比较注册码是否正确
return false;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return false;
}
return true;
}


在上面的checkSN中用到的bytes2string方法如下
public String bytes2HexString(byte[] b) {

byte[] hex ="0123456789ABCDEF".getBytes();
byte[] buff = new byte[2 * b.length];

for (int i = 0; i < b.length; i++) {
buff[2 * i] = hex[(b[i] >> 4) & 0x0f];
buff[2 * i + 1] = hex[b[i] & 0x0f];
}
return new String(buff);
}


布局文件如下
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/root">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="android程序破解演示实例1" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >

<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用户名:" />

<EditText
android:id="@+id/edit_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10" >

</EditText>

</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >

<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="注册码:" />

<EditText
android:id="@+id/edit_sn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10" />

</LinearLayout>

<Button
android:id="@+id/button_register"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="注册" />

</LinearLayout>


#3 破解过程

反编译
apk 文件成功后,会在当前的
outdir 目录下生成一系列目录与文件。其中
smali目录下存放了程序所有的反汇编代码,
res 目录则是程序中所有的资源文件,这些目录的子目录和文件与开发时的源码目录组织结构是一致的。如何寻找突破口是分析一个程序的关键。对于一般的
Android
来说,错误提示信息通常是指引关键代码的风向标,在错误提示附近一般是程序的核心验证代码,分析人员需要阅读

这些代码来理解软件的注册流程。错误提示是
Android 程序中的字符串资源,开发
Android 程序时,这些字符串可能硬编码到源码中,也可能引用自“
res\values”目录下的
strings.xml
文件, apk
文件在打包时,strings.xml
中的字符串被加密存储resources.arsc
文件保存到 apk
程序包中, apk
被成功反编译后这个文件也被解密出来了。

在软件注册失败时会弹出“无效用户名或注册码”,我们以此为线索来寻找关键代码。打开“
res\values\string.xml”文件,内容如下:

<?xml version="1.0" encoding="utf-8"?>

<resources>

<string name="app_name">Crackme0201</string>

<string name="hello_world">Hello world!</string>

<string name="menu_settings">Settings</string>

<string name="title_activity_main">crackme02</string>

<string name="info">Android程序破解演示实例</string>

<string name="username">用户名:
</string>

<string name="sn">注册码:
</string>

<string name="register">注 册</string>

<string name="hint_username">请输入用户名</string>

<string name="hint_sn">请输入16位的注册码</string>

<string name="unregister">程序未注册</string>

<string name="registered">程序已注册</string>

<string name="unsuccessed">无效用户名或注册码</string>

<string name="successed">恭喜您!注册成功</string>

</resources>

开发
Android 程序时,
String.xml 文件中的所有字符串资源都在“
gen/<packagename>/

R.java”文件的
String
类中被标识,每个字符串都有唯一的 int
类型索引值,使用 Apktool


编译
apk 文件后,所有的索引值保存在
string.xml 文件同目录下的
public.xml 文件中。

unsuccessed

id 值为
0x7f05000c,在
smali 目录中搜索含有内容为
0x7f05000c 的文件,

最后发现只有
MainActivity$1.smali 文件一处调用,代码如下:

# virtual methods

.method public onClick(Landroid/view/View;)V

.locals 4

.parameter "v"

.prologue

const/4 v3, 0x0

……

.line 32

#calls:

Lcom/droider/crackme0201/MainActivity;->checkSN(Ljava/lang/String;

Ljava/lang/String;)Z

invoke-static {v0, v1, v2}, Lcom/droider/crackme0201/MainActivity;-> #检查注册码是否合法

access$2(Lcom/droider/crackme0201/MainActivity;Ljava/lang/String;Ljava/

lang/String;)Z

move-result v0

if-nez v0, :cond_0
#如果结果不为0,就跳转到cond_0标号处

.line 34

iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;->

this$0:Lcom/droider/crackme0201/MainActivity;

.line 35

const v1, 0x7f05000c# unsuccessed字符串

.line 34

invoke-static {v0, v1, v3}, Landroid/widget/Toast;->

makeText(Landroid/content/Context;II)Landroid/widget/Toast;

move-result-object v0

.line 35

invoke-virtual {v0}, Landroid/widget/Toast;->show()V

.line 42

:goto_0

return-void

.line 37

:cond_0

iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;->

this$0:Lcom/droider/crackme0201/MainActivity;

.line 38

const v1, 0x7f05000d# successed字符串

.line 37

invoke-static {v0, v1, v3}, Landroid/widget/Toast;->

makeText(Landroid/content/Context;II)Landroid/widget/Toast;

move-result-object v0

.line 38

invoke-virtual {v0}, Landroid/widget/Toast;->show()V

.line 39

iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;->

this$0:Lcom/droider/crackme0201/MainActivity;

#getter for: Lcom/droider/crackme0201/MainActivity;->btn_register:Landroid/

widget/Button;

invoke-static {v0}, Lcom/droider/crackme0201/MainActivity;->

access$3(Lcom/droider/crackme0201/MainActivity;)Landroid/widget

/Button;

move-result-object v0

invoke-virtual {v0, v3}, Landroid/widget/Button;->setEnabled(Z)V#设置注册按钮不可用

.line 40

iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;->

this$0:Lcom/droider/crackme0201/MainActivity;

const v1, 0x7f05000b# registered字符串,模拟注册成功

invoke-virtual {v0, v1}, Lcom/droider/crackme0201/MainActivity;->

setTitle(I)V

goto :goto_0

.end method

Smali
代码中添加的注释使用井号“
#”开头,“
.line 32”行调用了
checkSN()函数进行注册码的合法检查,接着下面有如下两行代码:

move-result v0

if-nez v0, :cond_0

checkSN()函数返回
Boolean
类型的值。这里的第一行代码将返回的结果保存到 v0
寄存器中,第二行代码对
v0
进行判断,如果 v0
的值不为零,即条件为真的情况下,跳转到 cond_0标号处,反之,程序顺利向下执行。

分析可以发现,“
.line 32”行的代码“
if-nez v0, :cond_0”是程序的破解点。
if-nez
是 Dalvik
指令集中的一个条件跳转指令,类似的还有 if-eqz、
if-gez、
if-lez
等。这些指令会在本书第
3 章进行介绍,读者在这里只需要知道,与
if-nez 指令功能相反的指令为if-eqz,表示比较结果为
0
或相等时进行跳转。用任意一款文本编辑器打开
MainActivity$1.smali 文件,将“
.line 32”行的代码“
if-nezv0, :cond_0”修改为“
if-eqz v0, :cond_0”,保存后退出,代码就算修改完成了。修改完
Smali
文件代码后,需要将修改后的文件重新进行编译打包成
apk 文件,签名后安装,随意输入,可以发现注册成功。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: