美文网首页ida
Android逆向练习 crackme NDKapp

Android逆向练习 crackme NDKapp

作者: h080294 | 来源:发表于2017-10-28 03:15 被阅读109次

    应用如下,
    1、点击执行功能,弹出弹窗;
    2、弹窗中点击取消退出应用;点击确定后跳到注册页面。
    3、注册页面完整注册后,弹窗提示,之后退出app

    一、分析apk

    1、class MyApp

    加载了一个本地库,有三个native方法,initSN()、saveSN(String)、work()。在onCreate()中执行了initSN()方法。

    public class MyApp extends Application
    {
      public static int m = 0;
      
      static{
        System.loadLibrary("juan"); }
    
      public native void initSN();
    
      public void onCreate(){
        initSN();
        super.onCreate();}
    
      public native void saveSN(String paramString);
    
      public native void work();
    }
    
    2、class MainActivity

    主要看onCreate()方法,启动后读取MyApp的成员m,并把值赋给int i。然后根据i的值,定义不同的string。

      private static String workString;
      public void work(String paramString){
        workString = paramString;
      }
    
      public void onCreate(Bundle paramBundle)
      {
        super.onCreate(paramBundle);
        setContentView(2130903040);
        ((MyApp)getApplication());
        int i = MyApp.m;
        String str;
        if (i == 0) {
          str = "-未注册";
        }
        for (;;)  # 这里的反编译是有问题的,通过上一篇章,我们知道这块是一个循环,不影响代码的阅读
        {
          setTitle("NDK保护与重启验证演示程序" + str);
          this.btn1 = ((Button)findViewById(2131165184));
          this.btn1.setOnClickListener(new MainActivity.1(this));
          return;
          if (i == 1) {
            str = "-正式版";
          } else if (i == 2) {
            str = "-专业版";
          } else if (i == 3) {
            str = "-企业版";
          } else if (i == 4) {
            str = "-专供版";
          } else {
            str = "-未知版";
          }
        }
      }
    
    3、按钮的OnClickListener

    跟进button的OnClickListener事件,先判断了MyApp的m成员是否为0,如果为0就走注册的逻辑。不为0,调用native的work()方法,然后弹对应的Toast提示--MainActivity.access$0(),其实就是private static String workString的值。而workString的值又是从work(String)方法而来。

    class MainActivity$1
      implements View.OnClickListener
    {
      MainActivity$1(MainActivity paramMainActivity) {}
      
      public void onClick(View paramView)
      {
        ((MyApp)this.this$0.getApplication());
        if (MyApp.m == 0)
        {
          this.this$0.doRegister();
          return;
        }
        ((MyApp)this.this$0.getApplication()).work();
        Toast.makeText(this.this$0.getApplicationContext(), MainActivity.access$0(), 0).show();
      }
    }
    

    二、尝试修改app

    1、在MyApp中,重新给成员m赋值

    修改方法就不介绍了,直接在smali添加两行,将自定义的值赋给m。

    const /4 v0, 0x3; #企业版
    sput v0, Lcom/droider/ndkapp/MyApp;m:I
    

    打包进行验证,发现点击执行功能后,还是弹出了需要注册的弹窗,所以直接赋值的路不通了。还是看onClick方法,我们之前分析到了要调用native的work方法,所以就得深入libjuan.so进行查看了。

    三、分析so文件

    我用的是mac,工具IDA,因为是初次使用,所以也记录下初始化过程。

    1、导入jni.h

    至于为什要要导入,是因为IDA不能识别JNI结构体,影响反汇编代码的阅读。
    File -- Liad file -- Parse C header file,选择jni.h文件。这里我用了Android sdk的NDK目录下的jni文件:
    /android-sdk-macosx/ndk-bundle/platforms/android-24/arch-arm/usr/include/jni.h

    按照介绍,注释掉开头的 #include <sys/cdefs.h>,#include <stdarg.h>。将#define JNIEXPORT attribute ((visibility ("default")))注释掉,改成#define JNIEXPORT

    image.png

    导入完成后会有成功提示,如果报错,就安装报错的提示修改。注意copy文件,不要影响原ndk的jni文件。

    2、structure中添加JNINativeInterface

    structures选项卡汇总,将JNINativeInterface和JNIInvokeInterface添加进去。

    添加成功后,回到主页面,在0x240处点击右键,可以看到已经能够解析出JNINativeInterface.GetStaticFiledID函数了。

    3、搜索函数

    app中有3个native方法,initSN()、saveSN(String)、work()。在左侧的function搜索函数名,发现并没有搜到有用的内容,所以到JNI_OnLoad中一探究竟。

    在.text:000013AC LDR PC, [R12,#JNINativeInterface.RegisterNatives]这行,明显的看到了RegisterNatives,其实就是把本地函数和java类方法关联起来。

    而RegisterNatives函数需要传进来class(native methods),此处传的是_data_start首地址。跟进看看发现数据如下,看到了熟悉的方法initSn、saveSn、work。所以得出结论:
    n1对应着initSn()方法
    n2对应着saveSn(String)方法
    n3对应着work()方法

    4、分析函数

    前面的分析中,我们点击执行功能会调用work方法,所以先看n3。细节的地方还不太懂,能看出大概的意思。先执行n1(initSN)方法,然后getValue取到值,再对值分别做比较1、2、3、4,跳转各个分支,并且在最后都调用了callWork方法。

    调用了com/droider/ndkapp/MainActivity类的work(String)方法,work(String)方法是给String workString赋值的,也就是toast的string文案。

    回到n1(initSN)函数
    先是打开(fopen)sd卡中的reg.dat文件,经过一些判断没问题后读到内存中(fread)。对比几个字符串是否相等后,跳转不同分支,但最后也都执行setValue方法。

    setValue函数,其实就是给MyApp的m成员赋值的。

    n2(saveSn)函数:
    通过观察,写一个reg.dat文件,使用md5加密。

    5、修改

    n3函数主要是拿到initSN后的m值,根据对应的m值给出toast文案
    n2函数就是写数据reg.data
    n1函数主要是读数据,然后经过判断给MyApp的m成员赋值

    所以直接修改n1的赋值相关逻辑是比较的好的方法。这里修改的思路是直接学习来的,仍以判断条件处入手。比如我们认定3号了(企业版),就得让setValue到“3号位置”。这里学习到的技巧就是直接修改字节码,让CMP比较结果为真即可。

    ...........
    .text:000015DC                 ADD     R1, PC, R1      ; "b2db1185c9e5b88d9b70d7b3278a4947"
    .text:000015E0                 BL      strcmp
    .text:000015E4                 CMP     R0, #0
    .text:000015E8                 BEQ     loc_163C
    ...........
    

    查看字节码,该条指令字节码为00 00 50 E3,将0x15E7修改成E1,这样相当于CMP R0,R0,也就是比较结果为真。修改完成后重新打包安装。

    6、成功

    由于sd中已有reg.data文件,所以点击执行后直接弹toast提示,这样标明我们的破解成功了。

    四、题外话

    saveSn的时候采用的是MD5加密算法,开发者提供了4个加密的字符串12345678、2345678、32345678、42345678。用MD5验证下,分别对应着25d55ad283aa400af464c76d713c07ad、08e0750210f66396eb83957973705aad、b2db1185c9e5b88d9b70d7b3278a4947、18e56d777d194c4d589046d62801501c。

    也就说4个注册码分别为上面的字符串

    APK连接:
    链接: https://pan.baidu.com/s/1i5CHQvV 密码: umqp

    相关文章

      网友评论

        本文标题:Android逆向练习 crackme NDKapp

        本文链接:https://www.haomeiwen.com/subject/snjdpxtx.html