分析Android程序之破解第一个程序

简介: 分析Android程序之破解第一个程序

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


1. 反编译APK文件


ApkTool是跨平台的工具,可以在windows平台与linux平台下直接使用。使用前到:http://code.google.com/p/android-apktool/  下载ApkTool,目前最新版本为1.4.3,Windows平台需要下载apktool1.4.3.tar.bz2与apktool-install-windows-r04-brut1.tar.bz2两个压缩包,如果是linux系统则需要下载apktool1.4.3.tar.bz2与apktool-install-linux-r04-brut1.tar.bz2,将下载后的文件解压到同一目录下。进入到命令行的解压目录下,执行apktool命令会列出程序的用法:


反编译apk文件的命令为: apktool  d[ecode]  [OPTS]  <file.apk>  [<dir>]


编译apk文件的命令为: apktool  b[uild]  [OPTS]  [<app_path>]  [<out_file>]


那么在命令行下进入到apktool工具目录,输入命令:


$ .
        /apktool  
        d  
        /home/fuhd/apk/gnapk/nice/com
        .
        nice
        .main.apk  outdir

   

稍等片刻,程序就会反编译完成,如图:


网络异常,图片无法展示
|


2. 分析APK文件


如上例,反编译apk文件成功后,会在当前的outdir目录下生成一系列目录与文件。其中smali目录下存放了程序所有的反汇编代码,res目录则是程序中所有的资源文件,这些目录的子目录和文件与开发时的源码目录组织结构是一致的。


如何寻找突破口是分析一个程序的关键。对于一般Android来说,错误提示信息通常是指引关键代码的风向标。以书中的注册示例为例,在错误提示附近一般是程序的核心验证代码,分析人员需要阅读这些代码来理解软件的注册流程。


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


以书中2.1.2节运行程序时的错误提示,在软件注册失败时会Toast弹出“无效用户名或注册码”,我们以此为线索来寻找关键代码。打开“res/values/strings.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程序时,strings.xml文件中的所有字符串资源都在“gen/<packagename>/R.java”文件的String类中被标识,每个字符串都有唯一的int类型索引值,使用Apktool反编译apk文件后,所有的索引值保存在strings.xml文件同目录下的public.xml文件中。


从上面列表中找到“无效用户名或注册码”的字符串名称unsuccessed。打开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
        =
        "string" 
        name
        =
        "unsuccessed" 
        id
        =
        "0x7f05000c"
        />     
        <!-- 这是这一行 -->
        .......
        <
        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文件一处调用,代码如下:


# virtual methods
        .method 
        public 
        onClick(Landroid/view/View;)V
        .locals 
        4
        .parameter 
        "v"
        .prologue
        const
        /
        4 
        v3, 
        0x0
        ......
        .line 
        32
        #calls:
        Locm/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
        .............
        .............(略)
        .............
        .end method

   

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


move-result v0
        if
        -nez v0,  :cond_0

checkSN()函数返回Boolean类型的值。这里的第一行代码将返回的结果保存到v0寄存器中,第二行代码对v0进行判断,如果v0的值不为零,即条件为真的情况下,跳转到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 34”行使用iget-object指令获取MainActivity实例的引用。代码中的->this$0是内部类MainActivity$1中的一个synthetic字段,存储 的是父类MainActivity的引用,这是Java语言的一个特性,类似的还有->access$0,这一类代码会在后面进行详细介绍。“.line 35”行将v1寄存器传入unsuccessed字符串的id值,接着调用Toast;->makeText()创建字符串,然后调用Toast;->show()V方法弹出提示,最后.line 40行调用return-void函数返回。


如果代码跳转,会执行如下代码:


: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/widger/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

   

这段代码的功能是弹出注册成功提示,也就是说,上面的跳转如果成功意味着程序会成功注册。


3. 修改Smali文件代码


经过上一小节的分析可以发现,“.line 32”行的代码“if-nez v0,:cond_0”是程序的破解点。if-nez是Dalvik指令集中的一个条件跳转指令。类似的还有if-eqz,if-gez,if-lez等。这些指令会在后面blog中进行介绍,在这里只需要知道,与if-nez指令功能相反的指令为if-eqz,表示比较结果为0或相等时进行跳转。


用任意一款文本编辑器打开MainActivity$1.smali文件,将“.line 32”行的代码“if-nez v0,:cond_0”修改为“if-eqz v0,:cond_0”,保存后退出,代码就算修改完成了。


4. 重新编译APK文件并签名


修改完Smali文件代码后,需要将修改后的文件重新进行编译打包成apk文件。编译apk文件的命令格式为:


apktool  b[uild]  [OPTS]  [<app_path>]  [<out_file>],打开命令行进入到apktool工具的目录,执行以下命令:

/apktool  
        b  
        /home/fuhd/apk/gw/outdir/

   

不出意外的话,程序就会编译成功。编译成功 后会在outdir目录下生成dist目录,里面存放着编译成功的apk文件。编译生成的crackme02.apk没有签名,还不能安装测试,接下来需要使用signapk.jar工具对apk文件进行签名。signapk.jar是Android源码包中的一个签名工具。代码位于Android源码目录下的/build/tools/signapk/SignApk.java文件中,源码编译后可以在/out/host/linux-x86/framework目录中找到它。使用signapk.jar签名时需要提供签名文件,我们在此可以使用Android源码中提供的签名文件 testkey.pk8与testkey.x509.pem,它们位于Android源码的build/target/product/security目录。将signapk.jar,testkey.x509.pem,testkey.pk8,3个文件放到同一目录,然后在命令提示符下输入如下命令对APK文件进行签名:

$ java -jar signapk.jar testkey.x509.pem testkey.pk8 
        /home/fuhd/apk/gw/outdir/crackme02
        .apk  crackme02Sign.apk

签名成功后会在同目录下生成crackme02sign.apk文件。


5. 安装测试


现在是时候测试修改后的成果了。启动一个Android AVD,或者使用数据线连接手机与电脑,然后在命令提示符下执行以下命令安装破解后的程序:

$ adb 
        install 
        crackme02sign.apk
相关文章
|
20天前
|
安全 Android开发 数据安全/隐私保护
深入探讨iOS与Android系统安全性对比分析
在移动操作系统领域,iOS和Android无疑是两大巨头。本文从技术角度出发,对这两个系统的架构、安全机制以及用户隐私保护等方面进行了详细的比较分析。通过深入探讨,我们旨在揭示两个系统在安全性方面的差异,并为用户提供一些实用的安全建议。
|
2月前
|
开发工具 Android开发 Swift
安卓与iOS开发环境对比分析
在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统无疑是主角。它们各自拥有独特的特点和优势,为开发者提供了不同的开发环境和工具。本文将深入浅出地探讨安卓和iOS开发环境的主要差异,包括开发工具、编程语言、用户界面设计、性能优化以及市场覆盖等方面,旨在帮助初学者更好地理解两大平台的开发特点,并为他们选择合适的开发路径提供参考。通过比较分析,我们将揭示不同环境下的开发实践,以及如何根据项目需求和目标受众来选择最合适的开发平台。
51 2
|
28天前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
53 15
Android 系统缓存扫描与清理方法分析
|
1月前
|
存储 Linux Android开发
Android底层:通熟易懂分析binder:1.binder准备工作
本文详细介绍了Android Binder机制的准备工作,包括打开Binder驱动、内存映射(mmap)、启动Binder主线程等内容。通过分析系统调用和进程与驱动层的通信,解释了Binder如何实现进程间通信。文章还探讨了Binder主线程的启动流程及其在进程通信中的作用,最后总结了Binder准备工作的调用时机和重要性。
Android底层:通熟易懂分析binder:1.binder准备工作
|
2月前
|
安全 Android开发 数据安全/隐私保护
探索安卓与iOS的安全性差异:技术深度分析与实践建议
本文旨在深入探讨并比较Android和iOS两大移动操作系统在安全性方面的不同之处。通过详细的技术分析,揭示两者在架构设计、权限管理、应用生态及更新机制等方面的安全特性。同时,针对这些差异提出针对性的实践建议,旨在为开发者和用户提供增强移动设备安全性的参考。
141 3
|
1月前
|
开发工具 Android开发 Swift
安卓与iOS开发环境的差异性分析
【10月更文挑战第8天】 本文旨在探讨Android和iOS两大移动操作系统在开发环境上的不同,包括开发语言、工具、平台特性等方面。通过对这些差异性的分析,帮助开发者更好地理解两大平台,以便在项目开发中做出更合适的技术选择。
|
2月前
|
安全 Linux Android开发
探索安卓与iOS的安全性差异:技术深度分析
本文深入探讨了安卓(Android)和iOS两个主流操作系统平台在安全性方面的不同之处。通过比较它们在架构设计、系统更新机制、应用程序生态和隐私保护策略等方面的差异,揭示了每个平台独特的安全优势及潜在风险。此外,文章还讨论了用户在使用这些设备时可以采取的一些最佳实践,以增强个人数据的安全。
|
3月前
|
Java 开发工具 Android开发
安卓与iOS开发环境对比分析
【8月更文挑战第20天】在移动应用开发的广阔天地中,Android和iOS两大平台各自占据着重要的位置。本文将深入探讨这两种操作系统的开发环境,从编程语言到开发工具,从用户界面设计到性能优化,以及市场趋势对开发者选择的影响。我们旨在为读者提供一个全面的比较视角,帮助理解不同平台的优势与挑战,并为那些站在选择十字路口的开发者提供有价值的参考信息。
|
2月前
|
IDE 开发工具 Android开发
安卓与iOS开发环境对比分析
本文将探讨安卓和iOS这两大移动操作系统在开发环境上的差异,从工具、语言、框架到生态系统等多个角度进行比较。我们将深入了解各自的优势和劣势,并尝试为开发者提供一些实用的建议,以帮助他们根据自己的需求选择最适合的开发平台。
49 1
|
3月前
|
开发框架 Android开发 Swift
安卓与iOS应用开发对比分析
【8月更文挑战第20天】在移动应用开发的广阔天地中,安卓和iOS两大平台各占半壁江山。本文将深入探讨这两大操作系统在开发环境、编程语言、用户界面设计、性能优化及市场分布等方面的差异和特点。通过比较分析,旨在为开发者提供一个宏观的视角,帮助他们根据项目需求和目标受众选择最合适的开发平台。同时,文章还将讨论跨平台开发框架的利与弊,以及它们如何影响着移动应用的开发趋势。