首页
社区
课程
招聘
[旧帖] [原创][原创]解析apk中的KEY 0.00雪花
发表于: 2014-5-26 22:32 2265

[旧帖] [原创][原创]解析apk中的KEY 0.00雪花

2014-5-26 22:32
2265
首先在模拟器上运行apk,

 
点击‘爱过’或者‘wanniba’都会推出程序。看来这个apk是做了保护的。
放到真机上运行:

 正常运行,任意输入key:

 显示:‘Your Input is too short or too long!’。
利用dex2jar,得到jar文件,打开后,先看:onCreate()函数:

protected void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    setContentView(2130903040);
    setTitle(2131034118);
    new AlertDialog.Builder(this).setTitle("Hello").setMessage("Do you like playing in the dirt").setIcon(2130837505).setCancelable(false).setNegativeButton("爱过", new DialogInterface.OnClickListener()
    {
      public void onClick(DialogInterface paramDialogInterface, int paramInt)
      {
        if (nibadexingfu.a(MainActivity.this.getApplicationContext()).booleanValue())
          MainActivity.fuckwanniba(MainActivity.this.getApplicationContext());
        if (nibadexingfu.b(MainActivity.this.getApplicationContext()).booleanValue())
          MainActivity.fuckwanniba(MainActivity.this.getApplicationContext());
      }
    }).setPositiveButton("wanniba", new DialogInterface.OnClickListener()
    {
      public void onClick(DialogInterface paramDialogInterface, int paramInt)
      {
        if (nibadexingfu.a(MainActivity.this.getApplicationContext()).booleanValue())
        {
          MainActivity.wanniba();
          MainActivity.fuckwanniba(MainActivity.this.getApplicationContext());
        }
        if (nibadexingfu.b(MainActivity.this.getApplicationContext()).booleanValue())
        {
          MainActivity.wanniba();
          MainActivity.fuckwanniba(MainActivity.this.getApplicationContext());
        }
      }
    }).show();
从上面可以看出点击‘爱过’后,会调用nibadexingfu类中的a和b这两个函数,若返回值为TRUE,则调用MainActivity中的本地方法fuckwanniba()。而点击‘wanniba’后也会调用nibadexingfu类中的a和b两个函数,若返回值为True,则调用MainActivity中的本地方法wanniba()和fuckwanniba()。很容易想到,保护机制就存在于这些地方。

首先来看nibadexingfu类中的a和b这两个函数:
public class nibadexingfu
{
  private static String[] known_device_ids;
  private static String[] known_imsi_ids;

  static
  {
    String[] arrayOfString1 = new String[1];
    arrayOfString1[0] = "000000000000000";
    known_device_ids = arrayOfString1;
    String[] arrayOfString2 = new String[1];
    arrayOfString2[0] = "310260000000000";
    known_imsi_ids = arrayOfString2;
  }

  public static Boolean a(Context paramContext)
  {
    String str = ((TelephonyManager)paramContext.getSystemService("phone")).getDeviceId();
    str.equalsIgnoreCase(str);
    String[] arrayOfString = known_device_ids;
    int i = arrayOfString.length;
    for (int j = 0; ; j++)
    {
      if (j >= i);
      for (Boolean localBoolean = Boolean.valueOf(false); ; localBoolean = Boolean.valueOf(true))
      {
        return localBoolean;
        if (!arrayOfString[j].equalsIgnoreCase(str))
          break;
      }
    }
  }

  public static Boolean b(Context paramContext)
  {
    String str = ((TelephonyManager)paramContext.getSystemService("phone")).getSubscriberId();
    String[] arrayOfString = known_imsi_ids;
    int i = arrayOfString.length;
    for (int j = 0; ; j++)
    {
      if (j >= i);
      for (Boolean localBoolean = Boolean.valueOf(false); ; localBoolean = Boolean.valueOf(true))
      {
        return localBoolean;
        if (!arrayOfString[j].equalsIgnoreCase(str))
          break;
      }
    }
  }
}

大概可以了解到a函数通过获取DeviceID后,将其与已有的一个ID "000000000000000"进行了对比;b函数通过获取SubscriberId后,将其与已有的"310260000000000"进行对比。但是对比之后的具体操作就有些看不清楚了,所以还是得利用apktool来得到smali文件来了解准确的过程,分析如下:
#对类中的字段赋值
.method static constructor <clinit>()V
    .locals 4

    .prologue
    const/4 v3, 0x1

    const/4 v2, 0x0

    .line 15
    new-array v0, v3, [Ljava/lang/String;

    const-string v1, "000000000000000"

    aput-object v1, v0, v2

    sput-object v0, Lcom/reverse/iamwanniba/nibadexingfu;->known_device_ids:[Ljava/lang/String;

    .line 33
    new-array v0, v3, [Ljava/lang/String;

    const-string v1, "310260000000000"

    aput-object v1, v0, v2

    sput-object v0, Lcom/reverse/iamwanniba/nibadexingfu;->known_imsi_ids:[Ljava/lang/String;

    .line 34
    return-void
.end method

#检测模拟器
.method public static a(Landroid/content/Context;)Ljava/lang/Boolean;
    .locals 8
    .parameter "context"

    .prologue
    const/4 v4, 0x0 #v4赋值为0

    .line 19
    .line 20
    const-string v3, "phone"

    invoke-virtual {p0, v3}, Landroid/content/Context;->getSystemService(Ljava/lang/String;)Ljava/lang/Object;#获取系统服务

    move-result-object v2

    .line 19
    check-cast v2, Landroid/telephony/TelephonyManager;

    .line 22
    .local v2, telephonyManager:Landroid/telephony/TelephonyManager;
    invoke-virtual {v2}, Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String;    #获取设备号

    move-result-object v0

    .line 23
    .local v0, device_ids:Ljava/lang/String;
    invoke-virtual {v0, v0}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z

    .line 24
    sget-object v5, Lcom/reverse/iamwanniba/nibadexingfu;->known_device_ids:[Ljava/lang/String;

    array-length v6, v5 #v5是数组,v6存放数组长度

    move v3, v4

    :goto_0
    if-lt v3, v6, :cond_0 #小于跳至cond_0

    .line 29
    invoke-static {v4}, Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean;

    move-result-object v3  #获取到的Device与已知的DeviceId均不相等,随即返回false,说明运行环境不是模拟器

    :goto_1
    return-object v3

    .line 24
    :cond_0
    aget-object v1, v5, v3 #取v5中第v3个的元素放到v1中

    .line 25
    .local v1, know_deviceid:Ljava/lang/String;
    invoke-virtual {v1, v0}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z #相等则返回1,不等则返回0

    move-result v7

    if-eqz v7, :cond_1 #若等于0,说明获取到的DeviceId不与已知的DeviceId相等,则跳至cond_1

    .line 26
    const/4 v3, 0x1

    invoke-static {v3}, Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean;

    move-result-object v3

    goto :goto_1 #获取到的Device与已知的DeviceId相等,跳转至goto_1,随即返回True,说明运行环境不是模拟器

    .line 24
    :cond_1
    add-int/lit8 v3, v3, 0x1 #v3加一

    goto :goto_0#进行循环
.end method

函数b和函数a的过程基本一致,就不多讲了。

到此,我有些疑问,难道模拟器的那两个ID真的就是"000000000000000"和"310260000000000"吗?写个小程序来验证一下吧:

 
可以看出,果然如此。

接下来就来看看动态链接库里头的那几个本地函数的具体是干什么的:
先看一下这个Native函数的声明:
static native void fuckwanniba(Context paramContext);#参数为Context

  static native String jnicall1(String paramString);#参数为String

  static native String jnicall2(String paramString);# 参数为String

  static native String jnicall3(String paramString);# 参数为String

  static native String jnicall4(String paramString); #参数为String

  static native void wanniba();#无参数

利用IDA来分析动态链接库,
从Function window可以看到我们要分析的所有的函数:

 。
1.  Java_com_reverse_ilikewanniba_MainActivity_fuckwanniba分析如下:





 
 
 

 
 
 
 
 
 
 
 

通过这些分析可以看出,fuckwanniba这个函数内部实际上只是把之前分析的java层的那个保护机制写成了原生层代码的形式。
再来看wanniba这个函数:
 
主要是来退出程序的。
分析完上面这两个函数,发现此程序利用NDK写native代码来对程序进行了保护。

继续分析四个jnicall函数:
看看调用处的反编译代码:
((Button)findViewById(2131230726)).setOnClickListener(new View.OnClickListener()
    {
      public void onClick(View paramView)
      {
        String str1 = ((EditText)MainActivity.this.findViewById(2131230724)).getText().toString();
        if (str1.length() == 16)
        {
          String str2 = str1.toUpperCase();
          String str3 = MainActivity.jnicall1(str2);
          String str4 = MainActivity.jnicall2(str2);
          String str5 = MainActivity.jnicall3(str2);
          String str6 = MainActivity.jnicall4(str2);
          String str7 = str3.concat(str4).concat(str5).concat(str6);
          Toast.makeText(MainActivity.this, str7, 1).show();
        }
        while (true)
        {
          return;
          Toast.makeText(MainActivity.this, "Your Input is too short or too long!", 1).show();
        }
      }
    });
    ((Button)findViewById(2131230727)).setOnClickListener(new View.OnClickListener()
    {
      public void onClick(View paramView)
      {
        System.exit(0);
      }
    });
    if (paramBundle == null)
      getFragmentManager().beginTransaction().add(2131230720, new PlaceholderFragment()).commit();
  }

可以看出大致过程是:点击‘确认’后,获取到输入的KEY,转化成string,若长度为16,则分别调用四个jincall函数对此str进行处理,再将分别处理的结果连接起来,最后将连接好的string输出;若长度不为16,则会输出‘Your Input is too short or too long!’。

分析jnicall1:
 
 
 
可以看出若比对的两个byte与‘Sy’相同,则返回‘yeah’,否则返回‘ohno’。其他的几个函数:jnicall2,jnicall3,jnicall4和jnicall1基本相同。

再来看wanniba1:
 
 
 

可以看出wanniba2函数对父函数栈中的那个字符串进行了修改:每次取其中的两个字符,算出一个值,再将此值写回到栈中原来字符串的位置。循环两次,这样jnicall1栈中的字符串就变成了两个字符组成的字符串了。
再来看wanniba1,看wanniba1究竟是如何根据两个字符算出一个值来的:
 
(Src处的数据为:
 )
 
 
 
 

对于那些函数,如_floatsidf开始不知道到底是干什么的,查了相关资料后发现是:
该 ABI 不支持硬件辅助浮点计算。  作为替代,全部的浮点操作是通过来自编译器的 libgcc.a 静态库中的软件帮助程序函数完成。
查阅相关文档后,才找到了这些函数的具体含义,如上面分析。

接下来,我根据wanniba1里头的算法写了个c程序,用来求哪两个字符可以经过wanniba1中的算法得出字符S,如下:
int main()
{
  unsigned int a[2];// 存放那两个字符经转化后的对应的两个数,如‘1’对应ox0001
  unsigned int R4=0;// 作为计数器
  unsigned int R5=0;
  double d16=16.0;
  double d1;

  //进行穷举
  for(a[0]=0;a[0]<=15;a[0]++)
  {
    for(a[1]=0;a[1]<=15;a[1]++)
    {
      R4=0;
      R5=0;
      while(R4!=2)
      {
        d1=pow(d16,double(R4));
        R5=(d16/d1)*double(a[R4])+double(R5);
        R4++;
      }
      if(R5=='S')  //这里修改,求得不同的字符对应的那两个字符
      {
        if(a[0]>=10)
          a[0]=a[0]+0x37;
        else
          a[0]=a[0]+0x30;
        if(a[1]>=10)
          a[1]=a[1]+0x37;
        else
          a[1]=a[1]+0x30;

        printf("%c,%c---->>%c\n",a[0],a[1],R5);
      }
    }
  }
  getchar();
}
(注:写完这个程序后自己才发现,原来wanniba1中的那段算法其实就是将第一个字符转化后的整数当做16进制数的高四位,将第二个字符转化后的整数当做十六进制数的低四位,然后求出这个数的值)
结果:
 
 
 
 
 
 
 
 
这样就基本得到了key,但是还需要重新组织一下。
Jnicall1:
 
Jnicall2:
 
Jnicall3:
 
Jnicall4:

 
从这四个函数的具体的过程中可以看出:
字符  相关的byte
S  第1,2 byte
y  第3,4 byte
c  第5,7 byte
L  第6,8 byte
0  第9,12 byte
v  第10,11 byte
3  第14,13 byte
R  第15,14 byte
这样分析后就可以得出最终的key为:
5379643C37603325

将此key输入到apk中:

 ,成功了!!!

关于IDA动态调试此程序:
最开始,自己想在模拟器上对调试此apk的动态链接库。但是,这个程序没办法运行在模拟器上,这个怎么办呢?自己先尝试对smali文件进行了修改,绕过保护部分,然后进行重编译,但是编译时出了问题,这个方法行不通。
我又进行了另一种尝试:利用此apk中的动态链接库so来自己写一个apk,在自己的apk中调用那四个jnicall函数,接着动态调试自己写的这个apk,因为自己写的这个apk没有任何的保护,肯定可以在模拟器上运行调试。具体过程为:
创建工程,命令行下输入:
Android create project –n testsos –p testsos –t android-10 –k com.reverse.ilikewanniba –a MainActivity
(自己最开始是利用android-16,但是每次在IDA中下断点时老出现异常,了解后猜测可能和版本有关,结果换成android-10就好了,可以随意下断点)
这样创建好工程后,在工程目录下的libs文件夹中创建armeabi文件夹,最后将原apk的so文件复制进去。
利用eclipse将此工程载入,编写自己的apk,部分代码如下:
 
 
 


接下来,编译运行就行了。
最后,就可以随意利用IDA进行动态调试了。我主要利用动态调试来分析wanniba1中的那部分的算法,以及验证自己写的程序是否正确。

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 0
支持
分享
最新回复 (7)
雪    币: 103
活跃值: (126)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
2
注释 很详细!!!
2014-5-26 22:36
0
雪    币: 250
活跃值: (251)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
楼主分析得很到位哦~   nice
2014-5-27 16:53
0
雪    币: 341
活跃值: (85)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
4
毅力不错
2014-5-27 16:55
0
雪    币: 240
活跃值: (403)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
5
谢谢很精彩 , 楼主的模拟器是用官方的还是VirtualBox之类的 ?
2014-11-16 10:46
0
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
楼主好屌,不过我感觉与标题不符
2014-11-16 21:14
0
雪    币: 275
活跃值: (254)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
7
官方的。
2014-12-2 16:01
0
雪    币: 275
活跃值: (254)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
8
不好意思,第一次发。嘿嘿
2014-12-2 16:05
0
游客
登录 | 注册 方可回帖
返回
//