• Android apk逆向实战


    简介

    逆向Android apk其实是一个分析Android apk的一个过程,必须了解Android程序开发的流程、结构、语句分支、解密原理等等。

    功能

    破解一个注册验证程序(自写一个简单的注册验证程序,然后分析它,再破解它)。

    步骤

    1、编写一个简单的注册验证apk,关键代码如下:

        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 = toHexString(bytes, ""); //将计算结果转化成字符串
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < hexstr.length(); i += 2) {
                    sb.append(hexstr.charAt(i));
                }
                String userSN = sb.toString(); //计算出的SN        
                //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();
        }

    这个方法的主要功能是计算用户名与注册码是否匹配。

    运行效果如图:



    2、分析、破解

    下载开源工具ApkTool:http://code.google.com/p/android-apktool/

    破解Android apk的方法是将apk文件利用ApkTool反编译,生成Smali格式的反汇编代码,然后分析Smali文件的代码运行机制,找到程序的突破口进程修改,最后使用ApkTool

    重新编译生成apk文件并签名。

    命令格式如下:

    反编译apk:apktool d[ecode] [opts] <file.apl> [<dir>]

    编译apk:apktool b[uild] [opts] [<app_path>] [<out_file>]

    运行效果如图:



    反编译apk文件成功后,会在当前的outdir目录下(默然是apk文件名)生成一系列目录与文件,其中smali目录下存放了程序所有的反汇编代码,res目录则是程序中所有的资源文件,这些目录的子目录和文件与原程序的源码目录结构是一致的。

    如何寻找突破口是分析一个程序的关键,一般来说,错误提示信息通常是指引关键代码的风向标,在错误提示附近一般是程序的核心验证码。

    错误提示是Android 程序中的字符串资源,这些字符串可能硬编码到源代码中,也可能引用自 “res目录下的string.xml文件,apk打包时,string.xml中的字符串被加密存储为resources.arsc文件保存到apk程序包中。

    string.xml 详情如下:

    <resources>
    
        <string name="app_name">Crackme0201</string>
        <string name="menu_settings">Settings</string>
        <string name="title_activity_main">Crackme0201</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>


    对应加密文件(public.xml)详情:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <public type="drawable" name="ic_launcher" id="0x7f020001" />
        <public type="drawable" name="ic_action_search" id="0x7f020000" />
        <public type="layout" name="activity_main" id="0x7f030000" />
        <public type="dimen" name="padding_small" id="0x7f040000" />
        <public type="dimen" name="padding_medium" id="0x7f040001" />
        <public type="dimen" name="padding_large" id="0x7f040002" />
        <public type="string" name="app_name" id="0x7f050000" />
        <public type="string" name="hello_world" id="0x7f050001" />
        <public type="string" name="menu_settings" id="0x7f050002" />
        <public type="string" name="title_activity_main" id="0x7f050003" />
        <public type="string" name="info" id="0x7f050004" />
        <public type="string" name="username" id="0x7f050005" />
        <public type="string" name="sn" id="0x7f050006" />
        <public type="string" name="register" id="0x7f050007" />
        <public type="string" name="hint_username" id="0x7f050008" />
        <public type="string" name="hint_sn" id="0x7f050009" />
        <public type="string" name="unregister" id="0x7f05000a" />
        <public type="string" name="registered" id="0x7f05000b" />
        <public type="string" name="unsuccessed" id="0x7f05000c" />
        <public type="string" name="successed" id="0x7f05000d" />
        <public type="style" name="AppTheme" id="0x7f060000" />
        <public type="menu" name="activity_main" id="0x7f070000" />
        <public type="id" name="textView1" id="0x7f080000" />
        <public type="id" name="edit_username" id="0x7f080001" />
        <public type="id" name="edit_sn" id="0x7f080002" />
        <public type="id" name="button_register" id="0x7f080003" />
        <public type="id" name="menu_settings" id="0x7f080004" />
    </resources>


    unsuccessed的id值为0x7f05000c,在smali目录中搜索含有内容为0x7f05000c的文件,最后发现只有MainActivity$1.smali文件一处调用,代码如下:

    .class Lcom/droider/crackme0201/MainActivity$1;
    .super Ljava/lang/Object;
    .source "MainActivity.java"
    
    # interfaces
    .implements Landroid/view/View$OnClickListener;
    
    
    # annotations
    .annotation system Ldalvik/annotation/EnclosingMethod;
        value = Lcom/droider/crackme0201/MainActivity;->onCreate(Landroid/os/Bundle;)V
    .end annotation
    
    .annotation system Ldalvik/annotation/InnerClass;
        accessFlags = 0x0
        name = null
    .end annotation
    
    
    # instance fields
    .field final synthetic this$0:Lcom/droider/crackme0201/MainActivity;
    
    
    # direct methods
    .method constructor <init>(Lcom/droider/crackme0201/MainActivity;)V
        .locals 0
        .parameter
    
        .prologue
        .line 1
        iput-object p1, p0, Lcom/droider/crackme0201/MainActivity$1;->this$0:Lcom/droider/crackme0201/MainActivity;
    
        .line 29
        invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    
        return-void
    .end method
    
    
    # virtual methods
    .method public onClick(Landroid/view/View;)V
        .locals 4
        .parameter "v"
    
        .prologue
        const/4 v3, 0x0
    
        .line 32
        iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;->this$0:Lcom/droider/crackme0201/MainActivity;
    
        iget-object v1, p0, Lcom/droider/crackme0201/MainActivity$1;->this$0:Lcom/droider/crackme0201/MainActivity;
    
        #getter for: Lcom/droider/crackme0201/MainActivity;->edit_userName:Landroid/widget/EditText;
        invoke-static {v1}, Lcom/droider/crackme0201/MainActivity;->access$0(Lcom/droider/crackme0201/MainActivity;)Landroid/widget/EditText;
    
        move-result-object v1
    
        invoke-virtual {v1}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
    
        move-result-object v1
    
        invoke-interface {v1}, Landroid/text/Editable;->toString()Ljava/lang/String;
    
        move-result-object v1
    
        invoke-virtual {v1}, Ljava/lang/String;->trim()Ljava/lang/String;
    
        move-result-object v1
    
        .line 33
        iget-object v2, p0, Lcom/droider/crackme0201/MainActivity$1;->this$0:Lcom/droider/crackme0201/MainActivity;
    
        #getter for: Lcom/droider/crackme0201/MainActivity;->edit_sn:Landroid/widget/EditText;
        invoke-static {v2}, Lcom/droider/crackme0201/MainActivity;->access$1(Lcom/droider/crackme0201/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-virtual {v2}, Ljava/lang/String;->trim()Ljava/lang/String;
    
        move-result-object v2
    
        .line 32             #调用checkSN函数
        #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-neqz v0, :cond_0    #关键跳转  程序的破接口  把if-eqz v0  ---->if-eqz v0 即可
        .line 34#获取实例的引用
        iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;->this$0:Lcom/droider/crackme0201/MainActivity;
    
        .line 35                                   字符串压人v1寄存器
        const v1, 0x7f05000c注意!!!!!!!!!!!!
    
        .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
    
        .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
    
        invoke-virtual {v0, v1}, Lcom/droider/crackme0201/MainActivity;->setTitle(I)V
    
        goto :goto_0
    .end method


    分析完毕,重新编译生成apk文件并签名





    签名成功后会在同目录下生成signed.apk文件,如图:



    破解完成,放入模拟器运行下,可以了。

    小结

    通过实战破解一个简单的验证程序,了解Android程序的一般分析与破解流程,但在实际的分析过程中,接触的代码远比这些要复杂得多。


    下载

    实例及工具下载

  • 相关阅读:
    jQuery dataType指定为json的问题
    C# Post数据和接收简单示例【转】
    搜集的关于领域驱动设计(DDD)的理论知识
    WCF:(400) Bad Request
    关于SQL生成随机字符串
    如何通过禁用按钮避免jQuery.ajax重复请求
    注册自定义HTTP Handlers
    Node.js+Express on IIS
    iframe在IE下不能写cookie的解决
    node.js从全局目录里加载module
  • 原文地址:https://www.cnblogs.com/keanuyaoo/p/3294060.html
Copyright © 2020-2023  润新知