首页 > Android安全, CTF > 简单Android CrackMe分析

简单Android CrackMe分析

这是今年ISCC的一道Android CrackMe题目,相对简单的题,懂点ARM指令就差不多了,至于smali什么的,随便看看就好。大概玩一两个这样的CrackMe就对Android的逆向有个大概的了解了。不过后来发现这玩意是从非虫的《Android软件安全与逆向分析》上拿过来的原题,略显蛋疼。

这是个重启注册型APK,题目要求对其进行Patch,使得进入程序后就是企业版程序。首先,使用ApkTool GUI进行解包,发现这个App调用了一个NDK编写的so库文件,crackme6\lib\armeabi\libhack.so。把crackme6.apk重命名为crackme6.zip并进行解压处理,在解压之后的根目录可以得到classes.dex文件(crackme6Zip\classes.dex)。使用ApkTool GUI将其转换为jar文件。用JD-GUI把jar文件转换成Java代码,其中的MyApp加载了so文件,如下面的代码所示:

import android.app.Application;
public class MyApp extends Application {
  public static int m = 0;
  static {
    System.loadLibrary("hack");           // 加载hack
  }
  public native void initSN();
  public void onCreate() {
    initSN();
    super.onCreate();
  }
  public native void saveSN(String paramString);
  public native void work();
}

除此之外,还声明了一些函数如initSN、saveSN、work等,像是so文件的导出函数。RegActivity负责注册界面,核心代码如下:

public class RegActivity extends Activity 
{
  public void onCreate(Bundle paramBundle) 
{
    super.onCreate(paramBundle);
    setContentView(2130903041);
    this.btn_reg = ((Button)findViewById(2131165184));
    this.edit_sn = ((EditText)findViewById(2131165185));
    this.btn_reg.setOnClickListener(new View.OnClickListener()
    {
      public void onClick(View paramView)
      {
        String str = RegActivity.this.edit_sn.getText().toString().trim();
        if ((str == null) || (str.length() == 0))
        {
          Toast.makeText(RegActivity.this, "请输入注册码", 0).show();
          return;
        }
        ((MyApp)RegActivity.this.getApplication()).saveSN(str);
        new AlertDialog.Builder(RegActivity.this).setTitle("注册").
setMessage("注册码已保存,点击确定后程序将退出,请手动重新启动本程序!").
setPositiveButton("确定", new DialogInterface.OnClickListener()
        {
          public void onClick(DialogInterface paramDialogInterface, int paramInt)
          {
            Process.killProcess(Process.myPid());
          }
        }).show();
      }
    });
  }
}

可以看出这是一个重启注册型程序,中间调用MyApp.SaveSN保存输入的注册码,之后自动退出并需要手动重启。重启后,自然会在MainActivity中进行判断,核心代码如下:

public class MainActivity extends Activity
{
  public void doRegister() { /*提示注册*/ }
  public void onCreate(Bundle paramBundle)
  {
    ((MyApp)getApplication());
    int i = MyApp.m;            // 根据这个变量判断注册类型
    String str;
    if (i == 0) str = "-未注册";
    while (true) {
      setTitle("点击执行功能去注册" + str);
      this.btn1 = ((Button)findViewById(2131165184));
      this.btn1.setOnClickListener(new View.OnClickListener() {
        public void onClick(View paramView){
          ((MyApp)MainActivity.this.getApplication());
          if (MyApp.m == 0) { // 提示注册
            MainActivity.this.doRegister();
            return;
          }
          ((MyApp)MainActivity.this.getApplication()).work();
          Toast.makeText(MainActivity.this.getApplicationContext(), 
MainActivity.workString, 0).show(); // 注册成功提示语
        }
      });
      return;
      if (i == 1) { str = "-正式版"; continue;}
      if (i == 2) { str = "-专业版"; continue; }
      if (i == 3) { str = "-企业版"; continue; }
      if (i == 4) { str = "-专供版"; continue; }
      str = "-未知版";
    }
  }

可以确定work、initSN、saveSN都位于so文件内了,使用IDA对其进行逆向分析。没有看到这些导出函数,但是有三个比较可疑的函数n1,n2,n3,实际对应着这几个函数:
Android CrackMe IDA Export Table
n1对应initSN,n2对应saveSN,n3对应work。n2函数负责把接收到的注册码计算小写32位MD5值,并将其保存于/sdcard/reg.dat文件之中,验证过程位于n1函数:

.text:000012E8 04 00 A0 E1                 MOV     R0, R4          ; s1
.text:000012EC 01 10 8F E0                 ADD     R1, PC, R1      ; 25d55ad283aa400af464c76d713c07ad
.text:000012EC                                                     ; 12345678 MD5
.text:000012F0 49 FF FF EB                 BL      strcmp          ; 字符串比较
.text:000012F4 08 00 50 E1                 CMP     R0, R8          ; 是否相等
.text:000012F8 17 00 00 0A                 BEQ     loc_135C        ; 相等则跳转
.text:000012FC BC 10 9F E5                 LDR     R1, =(a08e0750210f663 - 0x130C)
.text:00001300 04 00 A0 E1                 MOV     R0, R4          ; s1
.text:00001304 01 10 8F E0                 ADD     R1, PC, R1      ; 08e0750210f66396eb83957973705aad
.text:00001304                                                     ; 22345678 MD5
.text:00001308 43 FF FF EB                 BL      strcmp
.text:0000130C 00 00 50 E3                 CMP     R0, #0
.text:00001310 15 00 00 0A                 BEQ     loc_136C
.text:00001314 A8 10 9F E5                 LDR     R1, =(aB2db1185c9e5b8 - 0x1324)
.text:00001318 04 00 A0 E1                 MOV     R0, R4          ; s1
.text:0000131C 01 10 8F E0                 ADD     R1, PC, R1      ; b2db1185c9e5b88d9b70d7b3278a4947
.text:0000131C                                                     ; 32345678 MD5
.text:00001320 3D FF FF EB                 BL      strcmp
.text:00001324 00 00 50 E3                 CMP     R0, #0
.text:00001328 13 00 00 0A                 BEQ     loc_137C
.text:0000132C 94 10 9F E5                 LDR     R1, =(a18e56d777d194c - 0x133C)
.text:00001330 04 00 A0 E1                 MOV     R0, R4          ; s1
.text:00001334 01 10 8F E0                 ADD     R1, PC, R1      ; 18e56d777d194c4d589046d62801501c
.text:00001334                                                     ; 42345678 MD5
.text:00001338 37 FF FF EB                 BL      strcmp
.text:0000133C 00 00 50 E3                 CMP     R0, #0
.text:00001340 04 10 A0 03                 MOVEQ   R1, #4
.text:00001344 07 00 A0 E1                 MOV     R0, R7
.text:00001348 08 10 A0 11                 MOVNE   R1, R8          ; R0为最开始传入的参数
.text:0000134C A9 FF FF EB                 BL      setValue        ; R1的值为0 1 2 3 4 其中0表示失败

上面是核心代码。initSN读取文件内容,并直接进行字符串比较,有如下几个MD5值:

注册码           MD5                                     意义
12345678        25d55ad283aa400af464c76d713c07ad        正式版
22345678        08e0750210f66396eb83957973705aad        专业版
32345678        b2db1185c9e5b88d9b70d7b3278a4947        企业版
42345678        18e56d777d194c4d589046d62801501c        专供版

还有其他两种状态,一种是initSN函数中打开文件失败、或者内存分配失败、或者注册码不正确,那么就提示“程序未注册,功能无法使用!”,还有一种异常状态,理论上来说不会存在“软件版本状态异常!”。
比较完成之后,通过setValue函数保存在某处内存之中。work函数通过getValue获取上述保存的值,然后进行判断:

.text:000014A0 10 40 2D E9                 STMFD   SP!, {R4,LR}
.text:000014A4 00 40 A0 E1                 MOV     R4, R0
.text:000014A8 6F FF FF EB                 BL      fnCheckRegSN
.text:000014AC 04 00 A0 E1                 MOV     R0, R4
.text:000014B0 35 FF FF EB                 BL      getValue        ; 读取比较结果
.text:000014B4 00 00 50 E3                 CMP     R0, #0          ; 注册失败
.text:000014B8 0C 00 00 0A                 BEQ     loc_14F0
.text:000014BC 01 00 50 E3                 CMP     R0, #1
.text:000014C0 13 00 00 0A                 BEQ     loc_1514
.text:000014C4 02 00 50 E3                 CMP     R0, #2
.text:000014C8 16 00 00 0A                 BEQ     loc_1528
.text:000014CC 03 00 50 E3                 CMP     R0, #3          ; 企业版
.text:000014D0 19 00 00 0A                 BEQ     fEnterpriseVersion
.text:000014D4 04 00 50 E3                 CMP     R0, #4
.text:000014D8 04 00 A0 E1                 MOV     R0, R4
.text:000014DC 08 00 00 0A                 BEQ     loc_1504
.text:000014E0 68 10 9F E5                 LDR     R1, =(aSPfCiicMckCabx - 0x14EC)
.text:000014E4 01 10 8F E0                 ADD     R1, PC, R1      ; 软件版本状态异常!
.text:000014E8 10 40 BD E8                 LDMFD   SP!, {R4,LR}
.text:000014EC B6 FF FF EA                 B       callWork

然后根据结果进行跳转:

.text:000014F0             ; ---------------------------------------------------------------------------
.text:000014F0
.text:000014F0             loc_14F0                                ; CODE XREF: fnShowRegResult+18j
.text:000014F0 5C 10 9F E5                 LDR     R1, =(aCilxPcKcIxjmqM - 0x1500)
.text:000014F4 04 00 A0 E1                 MOV     R0, R4
.text:000014F8 01 10 8F E0                 ADD     R1, PC, R1      ; 程序未注册,功能无法使用!
.text:000014FC 10 40 BD E8                 LDMFD   SP!, {R4,LR}
.text:00001500 B1 FF FF EA                 B       callWork
.text:00001504             ; ---------------------------------------------------------------------------
.text:00001504
.text:00001504             loc_1504                                ; CODE XREF: fnShowRegResult+3Cj
.text:00001504 4C 10 9F E5                 LDR     R1, =(aCdqsVcvifCfifU - 0x1510) ;
.text:00001508 01 10 8F E0                 ADD     R1, PC, R1      ; 感谢您使用专供版程序!
.text:0000150C 10 40 BD E8                 LDMFD   SP!, {R4,LR}
.text:00001510 AD FF FF EA                 B       callWork
.text:00001514             ; ---------------------------------------------------------------------------
.text:00001514
.text:00001514             loc_1514                                ; CODE XREF: fnShowRegResult+20j
.text:00001514 40 10 9F E5                 LDR     R1, =(aCdqsVcvisNfCng - 0x1524)
.text:00001518 04 00 A0 E1                 MOV     R0, R4
.text:0000151C 01 10 8F E0                 ADD     R1, PC, R1      ; 感谢您购买正式版程序!
.text:00001520 10 40 BD E8                 LDMFD   SP!, {R4,LR}
.text:00001524 A8 FF FF EA                 B       callWork
.text:00001528             ; ---------------------------------------------------------------------------
.text:00001528
.text:00001528             loc_1528                                ; CODE XREF: fnShowRegResult+28j
.text:00001528 30 10 9F E5                 LDR     R1, =(aCdqsVcvisNfFUf - 0x1538) ;
.text:0000152C 04 00 A0 E1                 MOV     R0, R4
.text:00001530 01 10 8F E0                 ADD     R1, PC, R1      ; 感谢您购买专业版程序!
.text:00001534 10 40 BD E8                 LDMFD   SP!, {R4,LR}
.text:00001538 A3 FF FF EA                 B       callWork
.text:0000153C             ; ---------------------------------------------------------------------------
.text:0000153C
.text:0000153C             fEnterpriseVersion                      ; CODE XREF: fnShowRegResult+30j
.text:0000153C 20 10 9F E5                 LDR     R1, =(asc_3A70 - 0x154C) ; 企业版注册成功
.text:00001540 04 00 A0 E1                 MOV     R0, R4
.text:00001544 01 10 8F E0                 ADD     R1, PC, R1      ; 感谢您购买企业版程序!
.text:00001548 10 40 BD E8                 LDMFD   SP!, {R4,LR}
.text:0000154C 9E FF FF EA                 B       callWork
.text:0000154C             ; End of function fnShowRegResult
.text:0000154C

当setValue设置的值为3时就是企业版。现在,我们可以修改initSN中的几个MOV语句,使得任意的比较结果、打开文件失败、分配内存失败都设置为3,那么不需要注册就提示时企业版了。

在IDA中回到initSN函数,以其中一个分支为例:
<Android CrackMe ARM Opcode
我们要把MOV R1, #2改为MOV R1,#3,那么通过16进制编辑器将偏移0×1370处的02改为03就行了,用同样的方法修改initSN中的分支。修改的地方如图中红色字节:
Android CrackMe ARM Opcode Patch
之后保存so文件并进行重新打包签名操作,安装到模拟器进行测试,点击按钮提示“感谢您购买企业版程序”,不再出现注册过程,结果如图:
成功破解这个Android CrackMe
文章中所提到的APK以及idb文件已经打包上传到了百度云,下载地址为:Android CrackMe
本文PDF版本阅读:简单Android CrackMe分析


觉得文章还不错?点击此处对作者进行打赏!


本文地址: 程序人生 >> 简单Android CrackMe分析
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!


更多



分类: Android安全, CTF 标签: , , ,
  1. 2013年7月5日07:30 | #1

    呵,老兄现在也开始搞移动开发啦?好久不见,最近还好吧:)

    [回复]

    代码疯子 回复:

    @Winson, 呵呵,对这个有兴趣就玩了,业余爱好者~ 最近还好,就是太忙:)

    [回复]

  2. 2013年8月2日21:32 | #2

    好久没 看看blog之类滴了..
    最近苦逼了很久~~

    [回复]

    代码疯子 回复:

    @tanglei, 还在苦逼吗,最近我也很忙,没看blog

    [回复]

  3. cq
    2013年9月2日18:10 | #3

    lz 怎么调试的呢 还是纯static呢

    [回复]

    代码疯子 回复:

    @cq, 直接IDA看的…比较简单,还不会调试native程序

    [回复]

  4. 小苍鹰
    2013年10月1日01:53 | #4

    兄弟,你好,看到你的博文不多,我也是搞逆向的,可否交个朋友,xiaocangying123@qq.com [em003] [em003] [em012]

    [回复]

  1. 本文目前尚无任何 trackbacks 和 pingbacks.