monodis是mono發(fā)行包里的一個(gè)工具,作用類似與ms的ildasm,可以把dotnet pe文件反編譯為msil文件(另外有個(gè)托管代碼的實(shí)現(xiàn)Mono.Cecil)。這個(gè)工具的實(shí)現(xiàn)很簡(jiǎn)單,就是根據(jù)PE文件的格式與規(guī)范去解析。選擇這個(gè)主題的原因有很多,首先PE文件作為進(jìn)行分析mono的基礎(chǔ),畢竟這里是metadata的來源;另外通過分析msil語言,可以為后續(xù)的VM執(zhí)行引擎做準(zhǔn)備,畢竟無論是jit還是aot,都是從msil到x86代碼的轉(zhuǎn)換,msil是第一步;當(dāng)然了解這個(gè)還可以為很多其他項(xiàng)目的分析做準(zhǔn)備,比如最近我脫離reflector很喜歡的一個(gè)工具ilspy,是把msil做ast分析轉(zhuǎn)換為C#。好了,簡(jiǎn)介到此為止,下面來具體分析下這個(gè)工具的源碼實(shí)現(xiàn)。
一.文件結(jié)構(gòu)及依賴
monodis在mono/dis目錄下,通過查看makefile可見mono依賴于libmono-$(API_VER).la,這部分功能主要和mono共用metadata解析部分的代碼,除了這些monodis自身的文件包括:
1.main.c:這個(gè)主要是提供參數(shù)解析及程序的入口,有參數(shù)可見monodis可以提供PE文件各部分信息的單獨(dú)輸出。
2.dump.c:這個(gè)是main.c里輸出各種table直接調(diào)用的方法,比如assembly,typeref,class,method,filed,event等等我們非常關(guān)心的一些東西。
3.get.c:主要提供一些stringfy的方法,即把MonoImage,MonoMethodSignature,MonoGenericContainer等Mono metadata相關(guān)的數(shù)據(jù)結(jié)構(gòu)表示為disassemble后的字符串。
4.dis-cil.c:這個(gè)可能是我們最關(guān)心的方法了,因?yàn)樗淖饔镁褪前袽onoMethodHeader輸出為msil代碼。
5.util.c:這個(gè)文件提供了四個(gè)工具方法:map和flags是查表方法,根據(jù)傳入的32位值得到相應(yīng)的字符串標(biāo)記,還有hex_dump和datadump,這兩個(gè)是把data直接輸出的。
二.反編譯的過程
1.加載file,解釋為assembly:
這一塊功能主要是由libmono實(shí)現(xiàn)的,具體代碼在image.c/do_mono_image_load函數(shù)中, 這一塊是和mono共用的,我們這里所關(guān)心的就是load_metadata所調(diào)用的load_tables方法,因?yàn)镸onoImage->tables是后續(xù)所有解析的來源,具體這一塊的實(shí)現(xiàn)這里不詳細(xì)的講,因?yàn)檫@個(gè)不是本文的重點(diǎn)。
2.由上一步的工作得到了monodis反編譯過程中需要的MonoImage結(jié)構(gòu)體數(shù)據(jù),下面就進(jìn)行了如下一系列的工作:
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);
由函數(shù)名即可得知,這是處理各個(gè)table呢。由于其中大部分工作都類似,所有這里只選擇分析其中最具代表性的dis_directive_assemblyref方法和最重要的distypes這個(gè)方法。
2.1:下面來著重講一下這個(gè)通用的工作流程,也就是以上方法通用的解析步驟,以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取得相應(yīng)的table得到MonoTableInfo,具體這個(gè)數(shù)據(jù)是從哪里來的,是第一步所做的工作,得到tableinfo之后,會(huì)對(duì)其進(jìn)行遍歷,然后用mono_metadata_decode_row解析到定義過的col變量里。這個(gè)col的大小及每個(gè)字段的定義都在row-indexes.h定義的各種枚舉里面,至于為什么以及每個(gè)字段的含義,請(qǐng)直接參考PE文件格式,另外有本書寫的也非常好《加密與解密》。取得col數(shù)據(jù)之后,就是把這些數(shù)據(jù)根據(jù)msil的文件格式要求輸出,如上例以及我測(cè)試用的一個(gè)PE文件輸出為:
.assembly extern mscorlib
{
.ver 2:0:0:0
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
}
其他的一些table輸出方式與上面一樣,只是格式不同而已。
2.2:現(xiàn)在來分析其中最重要的也是我們最關(guān)心的distype方法。這里其實(shí)有幾個(gè)重要的部分,依次進(jìn)行分析:
(1).輸出type的基本信息,包括name,namespace以及type相關(guān)的attribute,這部分邏輯是通用的,和dis_directive_assemblyref一樣的方式。
(2).輸出field,method,property,event等type的成員。分別對(duì)應(yīng)dis_field_list,dis_method_list,dis_property_list,dis_event_list方法。這里只分析其中典型的dis_method_list方法其中的dis_code方法。
首先由mono_image_rva_map獲取一個(gè)方法的偏移量指針,然后再由mono_metadata_parse_mh_full得到這個(gè)方法的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];
};
如果看著這個(gè)結(jié)構(gòu)體特別親切,那就對(duì)了。這個(gè)就是msil中的method的一個(gè)抽象,比如locals,exception clause,code等。
其實(shí)對(duì)_MonoMethodHeader的輸出,就是對(duì)這個(gè)結(jié)構(gòu)的一個(gè)stringfy。當(dāng)然由code到msil還需要一個(gè)過程,那就是disassemble_cil方法所做的工作了。這個(gè)過程就是把code根據(jù)其對(duì)應(yīng)的opcode進(jìn)行翻譯,解釋為我們平常熟悉的msil了。這個(gè)翻譯的過程很簡(jiǎn)單,具體代碼在dis-cil.c中的disassemble_cil方法,其中需要注意的一點(diǎn)就是會(huì)根據(jù)每個(gè)opcode的argument不同而翻譯為不同的表示。
好了,這就是monodis的一些關(guān)鍵部分,當(dāng)然還有很多沒有解釋也沒有必要去解釋,因?yàn)槎际穷愃频牟僮,把這個(gè)過程搞清楚則對(duì)metadata,image,msil等一些基礎(chǔ)性的概念有更深的了解,也是進(jìn)一步分析mono的基礎(chǔ)。