首页
社区
课程
招聘
[原创]LEB xx大师初步分析
发表于: 2014-12-5 23:29 14688

[原创]LEB xx大师初步分析

2014-12-5 23:29
14688
LEB xx大师初步分析
      来论坛很久了,一直没发过色和你们帖子帖,今天抽点时间用最近所学的东西做个帖子。

      简单做个自我介绍,小弟大学过程装备与控制工程专业,因对计算机比较感兴趣,毕业后就从事了相关工作,后来一直关注、尝试着逆向、安全方面的东西,但都是看书、自学、自练,不够系统,今年9月份参加了麦洛克菲第六期的培训,一开始是冲着驱动开发去的,效果很不错,自己以前积累的散乱的知识开始系统化起来,更重要的是还有意外惊喜-5天的移动安全课程。几天的课程和课后的练习使我可以独立分析一些apk。课程还没结束,精彩继续……
个人介绍到此,开始正题。

       都知道,安卓程序的一些核心功能,尤其是安全类产品,其核心功能大部分都是用c、c++实现的,ndk-build后生成so文件,以动态链接库的形式被java程序加载,调用。调用方式就是:System. loadLibrary ("TestJNI");这种形式加载so文件,然后在java类中声明一些native函数,这样java层就可以通过这个接口开调用对应的so文件中的函数了。public native String  stringFromJNI();

       此函数如何和c里面的函数对应起来?有2种方式:一种是用java提供的javah命令生成对应头文件,然后.c源文件中实现这些函数就可以了;另外一种方式是JNI_OnLoad中进行注册,因为loadLibrary 的时候系统会自动调用一次这个函数进行注册,

       那用的哪种方式又是如何判断的呢?方法很简单,看整个java类中一共有多少native函数(代码引导eclipse中ctrl+h搜索),然后在apk中的so文件查看导出函数,如果导出函数名字是Java_xxx_xx这种的刚好和native函数对应就是用的头文件的形式,如果有jni_onload,此函数里又涉及到了注册函数数组就是用的第二种方式了。第二种方式如下:

static const JNINativeMethod gMethods[] = { //定义批量注册的数组,是注册的关键部分
                { "func2", "()V", (void*) func2 }
};

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void *reserved) //这是JNI_OnLoad的声明,必须按照这样的方式声明
{
        JNIEnv* env = NULL; //注册时在JNIEnv中实现的,所以必须首先获取它
        jint result = -1;

        if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) //从JavaVM获取JNIEnv,一般使用1.4的版本
                return -1;

        jclass clazz;
        static const char* const kClassName = "mj/jnitest/MyObject";

        clazz = (*env)->FindClass(env, kClassName); //这里可以找到要注册的类,前提是这个类已经加载到java虚拟机中。 这里说明,动态库和有native方法的类之间,没有任何对应关系。

        if(clazz == NULL)
        {
                printf("cannot get class:%s\n", kClassName);
                return -1;
        }
}

      理清了这些,我们看看LEB是怎么做的吧。
      首先看所有的so文件:

Java代码中搜索 native :


      看这个类的部分内容:
public class AVLMNative {
    private int a;
    private String b;
    private String c;

    static {
        if(!LBEApplication.b()) {
            bo.a("avlm_jni");
        }
}
}
      静态代码块中加载了libavlm_jni.so
     bo.a("avlm_jni");  等价于 System.loadLibrary("avlm_jni");这个so也是我们今天要分析的主角。

      这个类的一个构造函数,主要需要知道2个string类型的参数是什么
public AVLMNative(String arg5, String arg6) {
        int v1 = 1;
        int v0 = 0;
        super();
        this.a = 0;
        this.b = arg5;
        this.c = arg6;
        this.a = AVLMNative.open(this.b, this.c, 0);
        if(this.a == 0) {
            throw new a();
        }

        if(!TextUtils.equals(this.g(), com.lbe.security.a.c("virus_pattern_ver"))) {
            com.lbe.security.a.a("virus_pattern_ver", this.g());
            v0 = 1;
        }

        if(!TextUtils.equals(this.h(), com.lbe.security.a.c("virus_engine_ver"))) {
            com.lbe.security.a.a("virus_engine_ver", this.h());
        }
        else {
            v1 = v0;
        }

        if(v1 != 0) {
            k.a().a(new Intent("com.lbe.security.action_refresh_ver"));
        }
    }

     全文搜索new AVLMNative得到如下结果:
this.c = new AVLMNative(bo.b("libavlm.so"), v1.getAbsolutePath());
可见AVLMNative.open(this.b, this.c, 0);其实就是把so的名字、路径传入底层,底层根据这2个参数打开什么东西,猜测就是加载libavlm.so ,而这个so是5个so文件中最大的240+k,可见是一个核心的文件。

     Java层可以暂时分析到这,接下来从libavlm_jni.so 入手。

在分析安卓程序里面的动态库文件时,一定要往IDA里面导入ndk中的jni.h这个头文件,不然会很纠结。导入的时候IDA可能会报错,解决办法很简单:把要导入的文件备份下,然后哪里报错删哪里。

      准备就绪后我们来导出表里看看:搜java_没有对应导出函数,说明用的是上面第二种说的原生函数的注册方式,那我们就把着手点放在JNI_Onload这个导出函数吧,看他里面是注册了哪些函数的。Ctrl+f5后如下;

signed int __fastcall JNI_OnLoad(int a1)
{
  signed int v1; // r3@2
  int v3; // [sp+8h] [bp-Ch]@1
  int v4; // [sp+Ch] [bp-8h]@3

  v3 = 0;
  if ( (*(int (__cdecl **)(int, int *))(*(_DWORD *)a1 + 24))(a1, &v3) )
  {
    v1 = -1;
  }
  else
  {
    v4 = (*(int (__fastcall **)(int, _DWORD))(*(_DWORD *)v3 + 24))(v3, "com/lbe/security/service/antivirus/AVLMNative");
    if ( v4 )
    {
      if ( (*(int (__fastcall **)(int, int, _DWORD, signed int))(*(_DWORD *)v3 + 860))(v3, v4, off_3E04, 19) >= 0 )
        v1 = 65540;
      else
        v1 = -1;
    }
    else
    {
      v1 = -1;
    }
}
       只是这样很难看的出有用东西,刚才导入的jni头文件现在起作用了,修改JNI_OnLoad的声明,JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void *reserved)
通过重新声明函数形式,修改参数名字,最后我们竟然得到了下面的代码!!!


      看来真是想逆就先要会正。
      可见注册了19个函数,刚好是java层需要的所有的原生函数。


     接下来就从这些函数入手吧,我们来看下大名鼎鼎的LEB的原生函数是如何实现的:
     当然先从open开始:,因为刚才java层的构造函数是首先调用这个函数的。

     再说一遍,函数原型很重要,所有原生函数的入口都会自带2个参数的,java自带的javah生成的头文件可以说明一切。结合java层open函数的声明,我们得到如下结论:
    private static native int open(String arg1, String arg2, int arg3) { };
AVLMNative的构造函数离调用的 AVLMNative.open(this.b, this.c, 0)

a1:虚拟机对象。
a2:类对象。
a3:so全名
a4:so路径
a5:调用的时候传递的0,暂且认定为类型吧。

     那我们继续y / n (修改函数声明/重命名参数)来创造奇迹吧。很像yes/no是不是……

小插曲:

     有时候会出现红框里的情况,右键点一下force call type就可以跟上面的展现形式一样的。

     稍微处理下就跟美容了一样。

     可以看出一共涉及到3个函数,AV_InitEx()  AV_Open()  AV_Close()
     首先看AV_open()这个函数

      一看这明显是在往什么结构里面塞东西嘛。而且大部分属性是解析出来的函数地址。
这里我们做了一个重要决定,要建立一个结构体提供给ida让ida再次碰到这种便宜的时候只要咱们制定了是该结构体的类型,ida就能自动识别出对应偏移的属性,而不是再以+xx的形式展现出来。好了,开造结构体,结合上面ini_onload里注册的函数数组来确定每个函数指针的参数类型和返回类型。
        在IDA中shift+f1添加一个这样的结构体,然后把malloc(0x6c)返回的地址赋值给的那个变量类型变为AddressInfo *,这样就完成了这个结构体的构造、使用。
struct AddressInfo
{
  void *handle;
  int flag;
  int tmp;
  int (__cdecl *AVLM_Init)(const char *, int);
  int (__cdecl *AVLM_InitEx)(const char *, int);
  int (__cdecl *AVLM_Release)();
  int (__cdecl *AVLM_Result_Free)(int);
  int (__cdecl *AVLM_Update)(const char *, int);
  int (__cdecl *AVLM_AVLPackEx)(const char *, const char *);
  void (__cdecl *AVLM_SetLogOpt)(int);
  void (__cdecl *AVLM_SetOutputOpt)(int);
  void (__cdecl *AVLM_SetScanOpt)(int);
  int (__cdecl *AVLM_Scan)(const char *);
  int (__cdecl *AVLM_ScanBuffer)(int, int, int);
  const char *(__cdecl *AVLM_GetVirnameEx)(int);
  const char *(__cdecl *AVLM_GetVirLOGDnfoEx)(int);
  int (__cdecl *AVLM_GetEmbedVirnameEx)(int);
  int (__cdecl *AVLM_GetAdsnameEx)(int);
  int (__cdecl *AVLM_GetUpdateNetTrafficUsed)();
  const char *(__cdecl *AVLM_GetSigLibVersion)();
  int (__cdecl *AVLM_GetEngineVersion)(PAddressInfo);
  int (__cdecl *AVLM_LogUpLoad)();
};


AV_InitEx()

AV_Close()

     这样一个函数就完整的分析完了,发现他是在初始化这个结构体(对象)。

     接下来再找个其他函数看看,如:AV_Scan()




     可见,AV_Scan()函数里面最终调用的是libavlm.so 里面的导出函数, 再看几个其他函数后会发现,大部分函数都是做一个跳转去调用libavlm.so里面的函数。相信libavlm.so里面的对应函数才是真正的实现,而我们现在看的这些基本都是类似框架的一个java->c的中转组件,这样我们很快就可以把整个libavlm_jni.so逆向出来了。libavlm_jni.so逆向出来有什么好处?一、准备的知道了libavlm.so里面真正重要的函数的参数形式。二、编译自己的libavlm_jni.so文件,里面的每个函数上添加日志输出,重新打包运行,可以查看操作什么功能的时候调用了什么函数。(前提是该apk可以重新打包发布执行,发现该程序再次打包会闪退,应该有校验签名)。 这是我想到的逆向libavlm_jni.so的意义,也算是对最近知识的梳理吧。

     哪位大侠知道往word里放入格式化后代码的好方法?我先从网页上格式化代码后粘贴到word里就变形了,后来从eclipse里复制过来才能勉强用,感觉也不是很好,没有前面的行数。

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 0
支持
分享
最新回复 (10)
雪    币: 6
活跃值: (1509)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
广告多于实际   各种培训  坑死无数人   培训出了一个模版  之后模版跳不出 这个框架
2014-12-5 23:42
0
雪    币: 546
活跃值: (1692)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
3
图文并茂,写的很好,赞一个。
2014-12-6 00:27
0
雪    币: 11207
活跃值: (17781)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
恭喜楼主迈入程序员的行列
2014-12-6 09:51
0
雪    币: 507
活跃值: (140)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
5
虽然不知道楼主分析了这么长时间在分析什么,有什么作用,但是看着还是蛮辛苦的,支持一下。
2014-12-6 10:12
0
雪    币: 23
活跃值: (91)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
师傅领进门修行在个人了,先把基本的东西快速学到……
2014-12-6 11:05
0
雪    币: 23
活跃值: (91)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
谢谢鼓励,第一次发长的帖子,没什么经验,从word里面往进一复制缩进什么的都没了。
2014-12-6 11:06
0
雪    币: 23
活跃值: (91)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
谢谢,原来主要做的java,现在处于转型期。
2014-12-6 11:07
0
雪    币: 23
活跃值: (91)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
谢谢,会再接再厉的。 第一次发长的帖子就把想到的、看到的写了下来。
2014-12-6 11:10
0
雪    币: 105
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
LEB?LBE?
2014-12-6 11:30
0
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
楼主好歹有分析过程,也有一个分析方向。虽然有广告之嫌,但是毕竟分析要远远多过广告。倒是你这一局废话多过实际内容。最犯病你这种回帖的人。
2015-1-13 17:58
0
游客
登录 | 注册 方可回帖
返回
//