您的位置:首页 > 其它

从monodis源码分析pe文件结构与msil反汇编

2011-04-23 16:41 633 查看
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的基础。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: