|
|
|
谁有《Shellcoder编程揭秘》英文版
Tahnks a million! |
|
谁有《Shellcoder编程揭秘》英文版
这个地方下不到啊 |
|
[注意]编程奖候选文章推荐活动进行中(元.7~元.9)
我厚颜自荐一下 Fahrenheit 【原创】自己写的文件分析器-PE DeCODER v1.0 http://bbs.pediy.com/showthread.php?threadid=33383 |
|
[原创]自己写的文件分析器-PE DeCODER v1.0( 看雪学院2006金秋读书季)
(上传文件在附件里) 说来惭愧,前几天自己写的一个文件分析器放在论坛上,有一位高手找到了我的分析器的BUG,并回了我的帖子。我仔细一想,确实存在一些不完善的地方,当晚我修改了程序代码,改进了不足的地方,并做了比较充分地测试。现在我把我的文件分析器的PE DeCODER v1.1放在论坛上,一是向使用过1.0版本且有运行错误的朋友表示一下歉意,是自己的疏忽没有做到尽量的完美,二也想给我找出程序BUG的这位朋友表示感谢,Thanks a million! 说到这个版本相对于1.0版本有那些改进,主要是体现在程序运行的稳定性得到了提高。在一些朋友的使用过程中,会出现内存错误的情况。针对如此,我发现异常情况都是出现在读取输入,输出表信息和前后两次读取不同exe文件的时候,所以我主要在程序里做了如下的几个方面的改进: 1)在DataDump对象里增加了Reset( void )成员函数。 2)改进了DataDump对象里输出输出表(Export_Table)的成员函数BOOL Show_EXPORT_TABLE( ifstream&, ofstream& ) const。 3)改进了DataDump对象里输出输出表(Import_Table)的成员函数BOOL Show_IMPORT_TABLE( ifstream&, ofstream& ) const。 4)在一些小细节上作了改进。 大家可能会问,难道文件头、可选文件头和节表的读取就没有改进吗?这是因为这三部分在一个exe文件中处于相对固定的位置。我们读取信息的关键是在于文件信息的定位,读取这一部分相对固定的信息,技术含量相对较低,出错的概率也非常小,故暂时没有对这一部分作出处理。下面我会逐个分析作出改进的原因和改进方案。 1)在DataDump对象里增加了Reset()成员函数。 在使用过程中,比如第一次输入文件名“c:\foo.exe”,分析完毕后再输入“C:\sanTC.exe”,进行下一个分析任务。那么第二次分析的结果可能会出错。具体表现是在输出表信息部分,会出现乱码。后来发现其实前一个是有输出表的,而后一个文件则没有输出表。所以在第二次分析时,本不该有输出表信息,但却显示乱码。这是由于前一次输出信息完毕以后,没有将节表中保存输出表信息结构回到原来的初始状态。第二次因为该指针未回到初始状态,让程序误以为存在输出表,就会显示出乱码。改进方法就是加一个Reset()成员函数。代码如下: VOID DataDump::Reset( VOID ) { if ( SECTION_HEADER ) delete SECTION_HEADER; SECTION_HEADER = NULL; ExVRk = ImVRk = 0x00000000; OPTIONAL_HEADER32.DataDirectory[0].VirtualAddress = OPTIONAL_HEADER32.DataDirectory[0].Size = 0x00000000; //Export table OPTIONAL_HEADER32.DataDirectory[1].VirtualAddress = OPTIONAL_HEADER32.DataDirectory[1].Size = 0x00000000; //Import table } 函数里释放了SECTION_HEADER指向的资源,因为每个程序结表的数目可能不同,让保存输出表信息结构回到初始状态,而指向输出表信息的指针所指的结构一旦分配了资源,就不必急于回收,因为每个文件的输出表的该结构大小都一样,新的值直接覆盖原来的值就行,最后交给对象的析构函数回收就可以了。既避免了内存泄漏,又能解决上述的问题。 2)成员函数BOOL Show_EXPORT_TABLE( ifstream&, ofstream& ) const是负责输出表的显示的,但是也负责读取和显示输出表成员所指向的函数和函数地址等一类的东东。在DataDump里还有一个成员函数是BOOL Set_EXPORT_TABLE( CHAR [], INT ),它是负责对EXPORT_DIRECTORY所指向的结构的读取,因为这一部分是固定的,可以预先读取,当然是在存在输出表的情况下。BOOL Show_EXPORT_TABLE( ifstream&, ofstream& ) const中的关键代码如下: if ( EXPORT_DIRECTORY->NumberOfFunctions ) { OrdalOffset = EXPORT_DIRECTORY->AddressOfNameOrdinals-ExVRk; // Get The Raw Offset Of NameOrdinals FunOffset = EXPORT_DIRECTORY->AddressOfFunctions-ExVRk; // Get The Raw Offset Of Functions NameOffset = EXPORT_DIRECTORY->AddressOfNames-ExVRk; // Get The Raw Offset Of Name_RVA_Arrays buf[31] = 0; INT* OrdalArray = new INT [EXPORT_DIRECTORY->NumberOfFunctions]; for ( INT i=0; i<EXPORT_DIRECTORY->NumberOfFunctions; i++ ) { OrdalArray[i] = -1; } for ( i=0; i<EXPORT_DIRECTORY->NumberOfNames; i++ ) { PE_file.seekg(OrdalOffset+2*i); // Get The Item Of The Serial Table PE_file.read(buf, 2); Offset = 0x00000000; ToNumeric((LPDWORD)&Offset, buf, 0, 2); // Store The Index In The Array "OrdalArray" OrdalArray[Offset] = i; } for ( i=0; i<EXPORT_DIRECTORY->NumberOfFunctions; i++ ) { fout<<setfill('0')<<" "<<setw(8)<<EXPORT_DIRECTORY->Base+i; // Get The Serial Of The Function if ( PE_file.eof() ) break; PE_file.seekg(FunOffset+4*i); PE_file.read(buf, 4); ToNumeric((LPDWORD)&Offset, buf, 0, 4); // Get The Item Of EAT into offset fout<<setfill('0')<<" "<<setw(8)<<Offset; if ( OrdalArray[i]!=-1 ) // If the OrdalArray[i]!=-1, which represents index i is injected into an Item ENT { PE_file.seekg(NameOffset+4*OrdalArray[i]); // OrdalArray[i] is the Index of the ENT PE_file.read(buf, 4); ToNumeric((LPDWORD)&Offset, buf, 0, 4); PE_file.seekg(Offset-ExVRk); PE_file.read(buf, 31); buf[31] = 0; fout<<setfill(' ')<<setw(34)<<buf<<endl; } else { fout<<" ---------------"<<endl; } } delete [] OrdalArray; } else { fout<<" ->No Exact Information !"<<endl; } } 在输出表存在的情况下,首先判断包含的输出函数数目,不为零则继续。分别读取输出序数表、输出函数地址表(EAT)和输出函数名称表(ENT)的物理首地址。然后分配一个数组OrdalArray[EXPORT_DIRECTORY->NumberOfFunctions],保存输出序数表项目的值。输出序数表和输出函数名称表(ENT)存在一种对应关系,输出序数表将ENT的数组索引映射到相应的输出地址表(EAT)条目。事实上,最关键的部分就在于此,这个数组直接关系到输出表信息是否能正确定位。然后程序按顺序读取EAT中的条目。程序根据相应项的值是否为非负数,判断这是不是一个ENT的索引值。我们知道输出函数可以以序数和名字两种方式输出,满足对应关系的,就是按名字输出的,可以到文件中读取名字字符串。而第i项的名字字符串的首地址,应该是ENT+4*OrdalArray[i]。有的朋友会问,这里为什么要用一个大小为NumberOfFunctions的数组,按名字输出的函数只有NumberOfNames个,会很费空间。这是和输出序数表的结构有关的。假如直接读入输出序数表的值,确实只要NumberOfNames个。但是假如在此情况下有OrdalArray[3]=2,它表示的是第2个输出函数的名称的首地址存放在ENT的第4项,即ENT[3];而在我们的程序中,OrdalArray[3]=2表示的是第3个输出函数的索引值是2,即第3个输出函数的名称的首地址存放在ENT[2]里。有朋友又会问,难道这些序数不是顺序存放的的吗?一般情况下是这样,但也有特例,Misrosoft Office2003中的Execel.exe的输出函数序数就不是按顺序存放的。在这里也不能人为的把数组整理成有序,关键的是当前项在数组OrdalArray里的索引值不能被改动。用较小的数组,在后面就要用到查找算法,不管怎么说,这都增加了程序的复杂度。两种方法各有利弊,前者则牺牲了空间换回了时间,后者是牺牲时间节省了空间。下面有一个粗略的比较: OrdalArray数组大小 时间复杂度 空间复杂度 附加代码量 NumberOfFunctions=n1 n1+n2 n1 小 NumberOfNames =n2 n2+n2*(n2-1)/2 n2 大 其中n2<=n1 一般情况下,输出函数中按名字输出的占到大部分,所以相比较而言,我选用了前面一种算法。后面一种也有可取之处,在按序号输出的情况占大多数的情况下效率更高。只有通过刚才所述的那种映射,才能找到正确的函数名称。有的时候,在分析报告里,可能会看到有的输出函数的RVA为0x00000000,名称显示不出来。这并不是程序错误。EAT中允许存在为0的条目,表示这个序数值没有代码或数据被输出。 3)成员函数BOOL Show_IMPORT_TABLE( ifstream&, ofstream& ) const是负责输入表的读取和显示的。和输出表不同,程序里没有BOOL Set_IMPORT_TABLE( CHAR [], INT )函数,因为输入表的不确定性更大。包括输入表描述符IMPORT_DESCRIPTOR的个数,以及每个Dll文件输入的函数的数目,所以这个函数里要负责输入表的读取和显示。程序的关键代码如下: if ( IMPORT_DESCRIPTOR.FirstThunk ) { Thunk = IMPORT_DESCRIPTOR.FirstThunk; } else { Thunk = IMPORT_DESCRIPTOR.OriginalFirstThunk; } DWORD OFOriginalFirstThunk = IMPORT_DESCRIPTOR.OriginalFirstThunk; if ( Thunk ) { for ( INT j=0; ; j++, OFOriginalFirstThunk += 4, Thunk+=4 ) { if ( PE_file.eof() ) break; PE_file.seekg(Thunk-ImVRk); PE_file.read(buf, 4); ToNumeric((LPDWORD)&ThunkValue, buf, 0, 4); if ( !ThunkValue ) { break; } fout<<" "<<setfill('0')<<setw(8)<<OFOriginalFirstThunk<<" "<<setw(8)<<ThunkValue; if ( !(ThunkValue & 0x80000000) ) // MSB Setted '0' Represents It Is A Pointer To IMAGE_IMPORT_BY_NAME { PE_file.seekg(ThunkValue-ImVRk); PE_file.read(buf, 2); ToNumeric((LPDWORD)&hint, buf, 0, 2); PE_file.read(buf, 28); fout<<" "<<setfill('0')<<setw(4)<<hint; ToString(buf, buf, 0, 28); fout<<setfill(' ')<<setw(30)<<buf; } else { fout<<" ---- ---------------"<<endl; } fout<<endl; } } 先要从IMPORT_DESCRIPTOR.FirstThunk中读取IMAGE_THUNK_DATA的首地址,如果不行再到IMPORT_DESCRIPTOR.OriginalFirstThunk中读取,这个顺序是不能颠倒的。虽然OriginalFirstThunk和FirstThunk在一个PE文件没有加载到内存中的时候是一样的,都是指向一个IMAGE_THUNK_DATA结构数组。既然OriginalFirstThunk和FirstThunk是相同的,那么为什么Windows要占用两个字段呢?其实在PE文件被PE加载器加载到内存中的时候这个加载器会自动把FirstThunk的值替换为API函数的真正入口,而OriginalFirstThunk只不过是用来反向查找函数名而已。那就是说OriginalFirstThunk有的时候可能为0x00000000,这样就不能对应到函数名称,而FirstThunk一定是存在有效值的,否则加载器找不到API函数的真正入口。在这里为了保证首次就得到有效的值,读取FirstThunk无疑是更好的选择。IMAGE_THUNK_DATA这个结构很有意思,因为在不同的时候这个结构代表着不同的含义。当这个双字的最高位为1时,表示函数是以序号的方式导入的;当最高位为0时,表示函数是以名称方式导入的,这时这个双字是一个RVA,指向一个IMAGE_IMPORT_BY_NAME结构,这个结构用来指定导入函数名称。所以程序里有那么一段是进行位运算,目的正在于此。 4)在一些小细节上作了改进。 在输出文件头时,其中有一个属性是TimeDateStamp,表示的是文件创建的时间,是自1970年1月1日以来的格林威治时间(GMT)计算的秒数。输出一串和大的数字,也不太明白他的意思,以该报它转化成我们日常生活中使用的日期时间格式。所以在中添加了以下代码: struct tm Time = *gmtime((CONST LONG *)&FILE_HEADER.TimeDateStamp); fout<<"----> 3. Created Time : "<<asctime(&Time); 在可选文件头里有一项属性是NumberOfRvaAndSizes,他的值一般为16。但是在一些特殊的PE文件,如miniPE里(稍后讨论),这个值会是1。而且,这个值在某些情况下也可能会比16大。所以,在NumberOfRvaAndSizes>16时,把它的值限定在16的范围内是有必要地,主要是考虑到后面计算输入输出表RVA和磁盘物理偏移的差值VRk的正确性。所以在BOOL DataDump::Set_OPTIONAL_HEADER32( CHAR buf[], INT size )中加入了下列语句: if ( OPTIONAL_HEADER32.NumberOfRvaAndSizes>16 ) OPTIONAL_HEADER32.NumberOfRvaAndSizes = 16; 输入表和输出表信息都安排在文件靠后的位置,为了防止读取信息时,超出文件末尾,在输出和输入表读取的函数里加入了检查是否到文件结束标志EOF的代码,可以比较好的处理体积比较小的PE文件,特别是对miniPE文件。关于miniPE的问题,一般都是经过人工修改的PE文件,省去了一些不需要的节表项目。缩小了输入表的体积,略去了输出表。下面是一个典型的miniPE文件,只有192字节。16进制编码如下,具体的可以参考上传文件里的一篇详细介绍的文章。。在PEDeCODER v1.1中加入了文件末尾判断就可以知道文件结束,避免运行时出错。但是还是得提醒一句,这样的PE文件一般是手工作品,并没有按照PE格式定义的存放顺序来保存数据,在用分析器读取信息是不一定能得到正确地结果,一些比较好的文件分析器亦然! Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F 00000000 4D 5A A1 7C 65 F4 77 BE BB 1C F0 77 33 C9 EB 08 MZ¡|eôw¾».ðw3Éë. 00000010 50 45 00 00 4C 01 01 00 51 41 80 3C 08 00 75 F9 PE..L...QA?..uù 00000020 54 51 EB 06 70 00 02 01 0B 01 50 6A F5 FF D6 50 TQë.p.....PjõÿÖP 00000030 B8 54 D3 F0 77 FF D0 C3 02 00 00 00 10 00 00 00 ¸TÓðwÿÐÃ........ 00000040 FF FF FF FF 00 00 10 00 02 00 00 00 02 00 00 00 ÿÿÿÿ............ 00000050 FF FF FF FF FF FF FF FF 04 00 00 00 FF FF FF FF ÿÿÿÿÿÿÿÿ....ÿÿÿÿ 00000060 C0 00 00 00 FF FF FF FF FF FF FF FF 03 00 FF FF À...ÿÿÿÿÿÿÿÿ..ÿÿ 00000070 00 00 10 00 00 10 00 00 00 00 10 00 00 10 00 00 ................ 00000080 FF FF FF FF 02 00 00 00 00 00 00 00 00 00 00 00 ÿÿÿÿ............ 00000090 A8 00 00 00 28 00 00 00 67 64 69 33 32 2E 64 6C ¨...(...gdi32.dl 000000A0 6C 00 00 00 00 00 00 00 C0 00 00 00 00 00 00 00 l.......À....... 000000B0 41 72 63 00 98 00 00 00 BC 00 00 00 AE 00 00 00 Arc.˜...¼...®... 这次还在输出报告的格式上作了些改变,目的是让报告看起来个有序和规范一点。 以上就是这次我租出改进的地方,谢谢大家,希望这次没有让大家失望。^_^ 我的邮箱是 : fahrenheit871116@163.com |
|
[原创]自己写的文件分析器-PE DeCODER v1.0( 看雪学院2006金秋读书季)
Thanks,i will fix it ! |
|
卡迈奇信息科技有限公司 招聘工程师 [上海]
想去,可惜我还没毕业... |
操作理由
RANk
{{ user_info.golds == '' ? 0 : user_info.golds }}
雪币
{{ experience }}
课程经验
{{ score }}
学习收益
{{study_duration_fmt}}
学习时长
基本信息
荣誉称号:
{{ honorary_title }}
能力排名:
No.{{ rank_num }}
等 级:
LV{{ rank_lv-100 }}
活跃值:
在线值:
浏览人数:{{ visits }}
最近活跃:{{ last_active_time }}
注册时间:{{ user_info.create_date_jsonfmt }}
勋章
兑换勋章
证书
证书查询 >
能力值