西西软件园多重安全检测下载网站、值得信赖的软件下载站!
软件
软件
文章
搜索

首页编程开发其它知识 → 使用monodis工具分析pe文件结构与msil反汇编

使用monodis工具分析pe文件结构与msil反汇编

相关软件相关文章发表评论 来源:西西整理时间:2011/4/24 7:47:55字体大小:A-A+

作者:西西点击:172次评论:0次标签: PE

  • 类型:音频处理大小:1M语言:中文 评分:5.1
  • 标签:
立即下载

monodis是mono发行包里的一个工具,作用类似与ms的ildasm,可以把dotnet pe文件反编译为msil文件(另外有个托管代码的实现Mono.Cecil)。这个工具的实现很简单,就是根据PE文件的格式与规范去解析。选择这个主题的原因有很多,首先PE文件作为进行分析mono的基础,毕竟这里是metadata的来源;另外通过分析msil语言,可以为后续的VM执行引擎做准备,毕竟无论是jit还是aot,都是从msil到x86代码的转换,msil是第一步;当然了解这个还可以为很多其他项目的分析做准备,比如最近我脱离reflector很喜欢的一个工具ilspy,是把msil做ast分析转换为C#。好了,简介到此为止,下面来具体分析下这个工具的源码实现。

一.文件结构及依赖
monodis在mono/dis目录下,通过查看makefile可见mono依赖于libmono-$(API_VER).la,这部分功能主要和mono共用metadata解析部分的代码,除了这些monodis自身的文件包括:

1.main.c:这个主要是提供参数解析及程序的入口,有参数可见monodis可以提供PE文件各部分信息的单独输出。

2.dump.c:这个是main.c里输出各种table直接调用的方法,比如assembly,typeref,class,method,filed,event等等我们非常关心的一些东西。

3.get.c:主要提供一些stringfy的方法,即把MonoImage,MonoMethodSignature,MonoGenericContainer等Mono metadata相关的数据结构表示为disassemble后的字符串。

4.dis-cil.c:这个可能是我们最关心的方法了,因为它的作用就是把MonoMethodHeader输出为msil代码。

5.util.c:这个文件提供了四个工具方法:map和flags是查表方法,根据传入的32位值得到相应的字符串标记,还有hex_dump和datadump,这两个是把data直接输出的。

 二.反编译的过程

1.加载file,解释为assembly:

这一块功能主要是由libmono实现的,具体代码在image.c/do_mono_image_load函数中, 这一块是和mono共用的,我们这里所关心的就是load_metadata所调用的load_tables方法,因为MonoImage->tables是后续所有解析的来源,具体这一块的实现这里不详细的讲,因为这个不是本文的重点。

2.由上一步的工作得到了monodis反编译过程中需要的MonoImage结构体数据,下面就进行了如下一系列的工作:

dump_header_data (img);
dis_directive_assemblyref (img);
dis_directive_assembly (img);
dis_directive_file (img);
dis_directive_mresource (img);
dis_directive_module (img);
dis_directive_moduleref (img);
dis_exported_types (img);
dis_nt_header (img);
dis_mresource (img);
dis_types (img, 0);
dis_data (img);

 由函数名即可得知,这是处理各个table呢。由于其中大部分工作都类似,所有这里只选择分析其中最具代表性的dis_directive_assemblyref方法和最重要的distypes这个方法。 

2.1:下面来着重讲一下这个通用的工作流程,也就是以上方法通用的解析步骤,以dis_directive_assemblyref为例:

static void
dis_directive_assemblyref (MonoImage *m)
{
    MonoTableInfo *t = &m->tables [MONO_TABLE_ASSEMBLYREF];
    guint32 cols [MONO_ASSEMBLYREF_SIZE];
    int i;
    
    if (t->base == NULL)
        return;

    for (i = 0; i < t->rows; i++){
        char *esc, *flags;

        mono_metadata_decode_row (t, i, cols, MONO_ASSEMBLYREF_SIZE);

        esc = get_escaped_name (mono_metadata_string_heap (m, cols [MONO_ASSEMBLYREF_NAME]));
        flags = assembly_flags (cols [MONO_ASSEMBLYREF_FLAGS]);
        
        fprintf (output,
             ".assembly extern %s%s\n"
             "{\n"
             "  .ver %d:%d:%d:%d\n",
             flags,
             esc,
             cols [MONO_ASSEMBLYREF_MAJOR_VERSION], cols [MONO_ASSEMBLYREF_MINOR_VERSION], 
             cols [MONO_ASSEMBLYREF_BUILD_NUMBER], cols [MONO_ASSEMBLYREF_REV_NUMBER]
            );
        dump_cattrs (m, MONO_TOKEN_ASSEMBLY_REF | (i + 1), "  ");
        if (cols [MONO_ASSEMBLYREF_CULTURE]){
            fprintf (output, "  .locale %s\n", mono_metadata_string_heap (m, cols [MONO_ASSEMBLYREF_CULTURE]));
        }
        if (cols [MONO_ASSEMBLYREF_PUBLIC_KEY]){
            const char* b = mono_metadata_blob_heap (m, cols [MONO_ASSEMBLYREF_PUBLIC_KEY]);
            int len = mono_metadata_decode_blob_size (b, &b);
            char *dump = data_dump (b, len, "\t\t");
            fprintf (output, "  .publickeytoken =%s", dump);
            g_free (dump);
        }
        fprintf (output, "}\n");
        g_free (flags);
        g_free (esc);
    }

首先从m->tables取得相应的table得到MonoTableInfo,具体这个数据是从哪里来的,是第一步所做的工作,得到tableinfo之后,会对其进行遍历,然后用mono_metadata_decode_row解析到定义过的col变量里。这个col的大小及每个字段的定义都在row-indexes.h定义的各种枚举里面,至于为什么以及每个字段的含义,请直接参考PE文件格式,另外有本书写的也非常好《加密与解密》。取得col数据之后,就是把这些数据根据msil的文件格式要求输出,如上例以及我测试用的一个PE文件输出为:

.assembly extern mscorlib
{
  .ver 2:0:0:0
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..

 其他的一些table输出方式与上面一样,只是格式不同而已。

2.2:现在来分析其中最重要的也是我们最关心的distype方法。这里其实有几个重要的部分,依次进行分析:

(1).输出type的基本信息,包括name,namespace以及type相关的attribute,这部分逻辑是通用的,和dis_directive_assemblyref一样的方式。

(2).输出field,method,property,event等type的成员。分别对应dis_field_list,dis_method_list,dis_property_list,dis_event_list方法。这里只分析其中典型的dis_method_list方法其中的dis_code方法。

首先由mono_image_rva_map获取一个方法的偏移量指针,然后再由mono_metadata_parse_mh_full得到这个方法的MonoMethodHeader。以下是其定义:

struct _MonoMethodHeader {
    const unsigned char  *code;
#ifdef MONO_SMALL_CONFIG
    guint16      code_size;
#else
    guint32      code_size;
#endif
    guint16      max_stack   : 15;
    unsigned int is_transient: 1; /* mono_metadata_free_mh () will actually free this header */
    unsigned int num_clauses : 15;
    /* if num_locals != 0, then the following apply: */
    unsigned int init_locals : 1;
    guint16      num_locals;
    MonoExceptionClause *clauses;
    MonoType    *locals [MONO_ZERO_LEN_ARRAY];
};

如果看着这个结构体特别亲切,那就对了。这个就是msil中的method的一个抽象,比如locals,exception clause,code等。

其实对_MonoMethodHeader的输出,就是对这个结构的一个stringfy。当然由code到msil还需要一个过程,那就是disassemble_cil方法所做的工作了。这个过程就是把code根据其对应的opcode进行翻译,解释为我们平常熟悉的msil了。这个翻译的过程很简单,具体代码在dis-cil.c中的disassemble_cil方法,其中需要注意的一点就是会根据每个opcode的argument不同而翻译为不同的表示。


好了,这就是monodis的一些关键部分,当然还有很多没有解释也没有必要去解释,因为都是类似的操作,把这个过程搞清楚则对metadata,image,msil等一些基础性的概念有更深的了解,也是进一步分析mono的基础。

    相关评论

    阅读本文后您有什么感想? 已有人给出评价!

    • 8 喜欢喜欢
    • 3 顶
    • 1 难过难过
    • 5 囧
    • 3 围观围观
    • 2 无聊无聊

    热门评论

    最新评论

    发表评论 查看所有评论(0)

    昵称:
    表情: 高兴 可 汗 我不要 害羞 好 下下下 送花 屎 亲亲
    字数: 0/500 (您的评论需要经过审核才能显示)