|
|
水帖 有64位系统的快去下XP64位测试系统
汇编没有用说做废就做废 |
|
|
水帖 有64位系统的快去下XP64位测试系统
我在另一贴子里给出过一XP64的下载地址和注删码,不知是否更方便: http://bbs.pediy.com/showthread.php?s=&threadid=9694 |
|
|
水帖 有64位系统的快去下XP64位测试系统
不知那个用的IDA是哪个版本,竟然反汇编出错误的结果. 看看我反汇编你的notepad.rar的结果! 其中RIP相对寻址是AMD64的新指令,如“CMP WORD PTR [RIP+ffff5b89H],5a4dH” 00000010000a450 488BC4 MOV RAX,RSP 000000010000a453 4881ECE8000000 SUB RSP,e8H 000000010000a45a 488958F8 MOV QWORD PTR [RAX+fffffff8H],RBX 000000010000a45e 488978F0 MOV QWORD PTR [RAX+fffffff0H],RDI 000000010000a462 488D4C2460 LEA RCX,[RSP+60H] 000000010000a467 FF156B6EFFFF CALL [RIP+ffff6e6bH] 000000010000a46d 90 NOP 000000010000a46e 66813D895BFFFF4D5A CMP WORD PTR [RIP+ffff5b89H],5a4dH 000000010000a477 740F JZ 000000010000a488 000000010000a479 33DB XOR EBX,EBX 000000010000a47b 895C2430 MOV DWORD PTR [RSP+30H],EBX 000000010000a47f 488D3D7A5BFFFF LEA RDI,[RIP+ffff5b7aH] 000000010000a486 EB7C JMP 000000010000a504 000000010000a488 486305AD5BFFFF MOVSXD RAX,DWORD PTR [RIP+ffff5badH] 000000010000a48f 488D3D6A5BFFFF LEA RDI,[RIP+ffff5b6aH] 000000010000a496 4803C7 ADD RAX,RDI 000000010000a499 813850450000 CMP DWORD PTR [RAX],4550H 000000010000a49f 7408 JZ 000000010000a4a9 000000010000a4a1 33DB XOR EBX,EBX 000000010000a4a3 895C2430 MOV DWORD PTR [RSP+30H],EBX 000000010000a4a7 EB5B JMP 000000010000a504 000000010000a4a9 0FB74818 MOVZX ECX,WORD PTR [RAX+18H] 000000010000a4ad 81F90B010000 CMP ECX,10bH 000000010000a4b3 7432 JZ 000000010000a4e7 000000010000a4b5 81F90B020000 CMP ECX,20bH 000000010000a4bb 7408 JZ 000000010000a4c5 000000010000a4bd 33DB XOR EBX,EBX 000000010000a4bf 895C2430 MOV DWORD PTR [RSP+30H],EBX 000000010000a4c3 EB3F JMP 000000010000a504 000000010000a4c5 83B8840000000E CMP DWORD PTR [RAX+84H],eH 000000010000a4cc 7708 JNBE 000000010000a4d6 000000010000a4ce 33DB XOR EBX,EBX 000000010000a4d0 895C2430 MOV DWORD PTR [RSP+30H],EBX 000000010000a4d4 EB2E JMP 000000010000a504 000000010000a4d6 33DB XOR EBX,EBX 000000010000a4d8 3998F8000000 CMP DWORD PTR [RAX+f8H],EBX 000000010000a4de 0F95C3 SETNZ BL 000000010000a4e1 895C2430 MOV DWORD PTR [RSP+30H],EBX 000000010000a4e5 EB1D JMP 000000010000a504 000000010000a4e7 8378740E CMP DWORD PTR [RAX+74H],eH 我知道了:IDA算出了绝对地址! 另外谁能解释它为什么比较5a4dH |
|
|
用IDA分析一个Function的问题。
会不会是向量迭代器的构造函数啊? |
|
|
[求助]问几个弱智的问题,请高手指点一二
这么多天没人肯回吗? |
|
|
请问如何使用编程方法读取寄存器
采臣・宁 说的是一个方法。看看我的代码在“Win32调试API的另类应用”贴里 |
|
|
|
|
|
|
|
|
|
|
|
关于ELF文件格式的实验
文章已完,虎头蛇尾。自我感觉最后是机械式的堆砌。有些结论是看来的,有些是实验来的,有些是意断来的。如有不对、不足的地方请大家跟贴补充,算是顶我吧(抛砖引玉的话就不说了)! |
|
|
Win32下支持16位、32位,64指令集的动态反汇编工具
支持实模式、保护模式、64位模式三种模式指令集的反汇编. 支持DOS系统COM和EXE可执行文件格式,支持32位和64位PE文件格式(windows可执行文件),支持32位和64位ELF文件格式(Linux可执行文件),共计六种文件格式。支持部分指令的虚拟执行调试。 |
|
|
[求助]问几个弱智的问题,请高手指点一二
Windows的SEH机制中FS的基地址是多少呢?如何避免能过CS、DS,ES,SS来访问它的内容,是通过段大小限制吗? |
|
|
关于ELF文件格式的实验
在Linux上我们常用的标准库函数都尽量被采用动态连接的方式连入我们的可执行文件。libc.so.6就是一个动态连接库。我们用一个最简单的程序开始我们的实践,这段代码大家都见过几万遍了: #include "stdio.h" int main() { printf("Hello,Linux!"); return 0; } [root@hangj elf]#gcc hello.c -o hello [root@hangj elf]# 为了能看到这个代码编译后的样子,我们用GDB加载它。 [root@hangj elf]#gdb hello 这段代码的反汇编结果如下: (gdb) disass main Dump of assembler code for function main: 0x08048370 <main+0>: push %ebp 0x08048371 <main+1>: mov %esp,%ebp 0x08048373 <main+3>: sub $0x8,%esp 0x08048376 <main+6>: and $0xfffffff0,%esp 0x08048379 <main+9>: mov $0x0,%eax 0x0804837e <main+14>: sub %eax,%esp 0x08048380 <main+16>: sub $0xc,%esp 0x08048383 <main+19>: push $0x804846c 0x08048388 <main+24>: call 0x80482b0 <_init+56> 0x0804838d <main+29>: add $0x10,%esp 0x08048390 <main+32>: mov $0x0,%eax 0x08048395 <main+37>: leave 0x08048396 <main+38>: ret 0x08048397 <main+39>: nop End of assembler dump. 显然唯一的一条函数调用“call 0x80482b0 <_init+56>”应该就是printf,看看0x80482b0处有什么? (gdb) x/10i 0x80482b0 0x80482b0 <_init+56>: jmp *0x8049570 0x80482b6 <_init+62>: push $0x8 0x80482bb <_init+67>: jmp 0x8048290 <_init+24> 0x80482c0 <_start>: xor %ebp,%ebp 0x80482c2 <_start+2>: pop %esi 0x80482c3 <_start+3>: mov %esp,%ecx 0x80482c5 <_start+5>: and $0xfffffff0,%esp 0x80482c8 <_start+8>: push %eax 0x80482c9 <_start+9>: push %esp 0x80482ca <_start+10>: push %edx 原来是一条跳转指令,要跳到的地址被保存在0x8049570处,按照我们对PE文件的经验0x8049570处一定存放着printf的真正地址。系统加载hello时从重定位表和动态表中知道我们引用了libc.so.6中的printf,然后它就加载libc.so.6,然后它就从libc.so.6的符号表中找到了printf,然后它就把hello的0x8049570处的四个字节改成printf的地址!一定是这样!那么这一条跳转就是去执行printf了。看看再说。 (gdb) x/4xw 0x8049570 0x8049570 <_GLOBAL_OFFSET_TABLE_+16>: 0x080482b6 0x00000000 0x00000000 0x0804948c “0x080482b6”这不是紧挨着跳转指令的下一条指令地址吗?怎么不是printf!Linux与Windows是不同的,《ELF规范》把0x80482b0处的这段东西叫做过程连接表(PLT)又把0x8049570处的东西称为全局偏移表(GOT),这两个概念我们不过多引入。请大家注意0x080482b6开始的那条push指令,它把0x8压入堆栈。0x8就我们的关键!我们退出GDB使用我们自己前面写下的工具。先打印符号表,由于内容太多我只把动态符号表中的情况摘抄在下面,动态符号表是类型为SHT_DYNSYM节。 .dynsym(type=SHT_DYNSYM) offset=0x174, vaddr=0x8048174, size=0x60 Sym: type=0(STT_NOTYPE) attrib=0 st_shndx=0, st_value=0x0(0), st_size=0 Sym:__libc_start_main type=2(STT_FUNC) attrib=1 st_shndx=0, st_value=0x0(0), st_size=239 Sym:printf type=2(STT_FUNC) attrib=1 st_shndx=0, st_value=0x0(0), st_size=57 Sym:_IO_stdin_used type=1(STT_OBJECT) attrib=1 st_shndx=14, st_value=0x8048468(134513768), st_size=4 Sym:_Jv_RegisterClasses type=0(STT_NOTYPE) attrib=2 st_shndx=0, st_value=0x0(0), st_size=0 Sym:__gmon_start__ type=0(STT_NOTYPE) attrib=2 st_shndx=0, st_value=0x0(0), st_size=0 可以看到动态符号表共有六项,除第1项保留不用,共引入5个外部符号,其中两个是函数,printf位于第3项。再打印重定位表: .rel.dyn(type=SHT_REL) offset=0x260, vaddr=0x8048260, size=0x8 type=6(R_386_GLOB_DAT) SYM=5 offset=0x804955c .rel.plt(type=SHT_REL) offset=0x268, vaddr=0x8048268, size=0x10 type=7(R_386_JMP_SLOT) SYM=1 offset=0x804956c type=7(R_386_JMP_SLOT) SYM=2 offset=0x8049570 在重定位表中请注意名为“.rel.plt”的重定位节,这个重定位表中只有两项,它们的重定位类型都是R_386_JMP_SLOT,它们与动态符号表中仅有的两个函数一一对应。第一个的符号表索引是1,对应着__libc_start_main,我们不关心;第二个符号表索引是2,恰与printf对应,我们看到它的r_offset字段的值是0x8049570,这个地址正好保存着printf要跳转到的地址。重定位信息告诉操作系统要修改这个地方可是系统并没修改,修改任务于是就必须由0x80482b6处的指令来完成。0x8――这个压入栈中的数值就我们的关键。重定位表中每个结构的大小恰是8个字节,于是你大胆猜测这个0x8就是外部函数的重定位信息在重定位表中的偏移量。这个猜测可以通过引入多个不同的函数加以验证。push指令后的跳转可认为是去调用一个函数,而push本身仅是向那个函数传递这个参数罢了,而那个函数一定会找到printf并调用它。那么为什么要如此大费周折呢,我想有些人一定猜到了结果:那个用重定位表偏移做参数的函数一定在得到printf地址后随后修改了0x8049570处保存的值,下一次再调用printf时就会直接由0x80482b0处跳到真正的函数体内了――怎么有点像Win9x中“VXD CALL”。通过引入多个外部函数我们又发现:多个外部函数在“.rel.plt”中的排列顺序与它们对应内容在GOT中的排列顺序完全一致,不同的是它们不是从GOT的偏移0开始的。 继续我们的实验,重新用GDB打开hello,在0x804838d处下一个断点,它将使程序在调用printf的语句之后停止不动。然后用r命令执行hello。 (gdb) b *0x804838d Breakpoint 1 at 0x804838d (gdb) r 经过几行输出,GDB最后打印了一条信息“Breakpoint 1, 0x0804838d in main ()”等待我们的命令。我们再来看一下0x8049570处的内容,它果然变了,由0x080482b6变成了0x005d62a0。这恰是printf的真正地址,我们还能看到printf还调用了vfprintf。 (gdb) x/4x 0x8049570 0x8049570 <_GLOBAL_OFFSET_TABLE_+16>: 0x005d62a0 0x00000000 0x00000000 0x0804948c (gdb) disass printf Dump of assembler code for function printf: 0x005d62a0 <printf+0>: push %ebp 0x005d62a1 <printf+1>: mov %esp,%ebp 0x005d62a3 <printf+3>: sub $0x10,%esp 0x005d62a6 <printf+6>: mov %ebx,0xfffffffc(%ebp) 0x005d62a9 <printf+9>: mov 0x8(%ebp),%edx 0x005d62ac <printf+12>: lea 0xc(%ebp),%ecx 0x005d62af <printf+15>: call 0x5a990d <__i686.get_pc_thunk.bx> 0x005d62b4 <printf+20>: add $0xd5d48,%ebx 0x005d62ba <printf+26>: mov %ecx,0x8(%esp) 0x005d62be <printf+30>: mov 0xfffffe7c(%ebx),%ecx 0x005d62c4 <printf+36>: mov %edx,0x4(%esp) 0x005d62c8 <printf+40>: mov (%ecx),%edx 0x005d62ca <printf+42>: mov %edx,(%esp) 0x005d62cd <printf+45>: call 0x5cd620 <vfprintf> 0x005d62d2 <printf+50>: mov 0xfffffffc(%ebp),%ebx 0x005d62d5 <printf+53>: mov %ebp,%esp 0x005d62d7 <printf+55>: pop %ebp 0x005d62d8 <printf+56>: ret 0x005d62d9 <printf+57>: nop 0x005d62da <printf+58>: nop 0x005d62db <printf+59>: nop 0x005d62dc <printf+60>: nop 0x005d62dd <printf+61>: nop 0x005d62de <printf+62>: nop 0x005d62df <printf+63>: nop End of assembler dump. (gdb) 实验内容就这么多,更进一步的细节我宁愿当它是一个黑盒,根据参数实现功能。 有一点我需要再三重复。在ELF文件中没有信息把printf与libc.so.6联系在一起,也就是说加载程序不知道printf的函数体在libc.so.6中。所以加载程序只能根据DT_NEEDED类型的动态结构加载所有程序需要的so模块,然后在所有so模块中寻找printf。这也是符号结构要带有“绑定类型”信息的原因。这样的连接方式有些好处。例如PE中没有使用的“懒模式”,不管程序执行过程中是否会用到,PE文件执行之前它引入的所有外部函数都必须被系统解析出来,而Linux下正如刚才看到的用到时才会解析。“懒模式”有它的危害,如果用到时才加载so模块,必然使程序的运行不够流畅,所以Linux一次性加载所有模块而用时解析函数应该是对此问题的解决方案。没有把模块与函数绑定在一起也使Linux的驱动开发者受益,可加载模块能够引用内核符号并能导出符号供其它模块使用。 |
|
|
关于ELF文件格式的实验
有了符号名和动态库的名字操作系统就可以为我们引入函数了,但在我们的程序中是谁会用这些外部函数呢,系统解析出来的函数地址应该给谁呢?这就是重定位表的功劳了! 重定位表的类型有两种SHT_REL和SHT_RELA,我们只谈SHT_REL。 typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; } Elf32_Rel; typedef struct { Elf64_Addr r_offset; Elf64_Xword r_info; } Elf64_Rel; Elf32_Rel或Elf64_Rel结构也很简单,它的r_offset字段给出了需要重定位的内容的地址。而它的r_info字段给出了两条信息,一条是与此重定位内容相关的符号;一条是重定位的类型。elf.h中定义了四个宏:ELF64_R_SYM、ELF64_R_TYPE、ELF32_R_SYM、ELF32_R_TYPE,分别用于从这个字段中于提取符号信息和重定位类型。符号信息就是一个符号表中的索引,32位下占这个字段的高24位,64位下占高32位。r_info字段剩余的8位或32位是重定位类型。在i386上从外部引入的动态函数重定位类型是R_386_JMP_SLOT。由这个类型的名称可以看出动态连接在Linux上是处理器密切相关的东西。刚说r_info字段包含了一个符号表中的索引,对于从外部引入的动态函数来说那个符号表就是“动态符号表”,它在节头结构中的类型值为SHT_DYNSYM。r_offset字段是一个地址,加载之初被r_offset指向的内容――也就是一个外部符号的地址――并不正确,操作系统就根据重定位信息引用的符号找到那个地址,然后修改r_offset所指向的内容。 下面给出打印ELF32文件中所有重定位结构的函数,这些函数同样将成为我们后续学习工具的一部分! void printRel(Elf32_Rel *pRel) { #define TEST_NAME(name)\ case name:\ strcpy(typeName,#name);\ break char typeName[20]; switch(ELF32_R_TYPE(pRel->r_info)) { TEST_NAME(R_386_NONE); TEST_NAME(R_386_32); TEST_NAME(R_386_PC32); TEST_NAME(R_386_GOT32); TEST_NAME(R_386_PLT32); TEST_NAME(R_386_COPY); TEST_NAME(R_386_GLOB_DAT); TEST_NAME(R_386_JMP_SLOT); TEST_NAME(R_386_RELATIVE); TEST_NAME(R_386_GOTOFF); TEST_NAME(R_386_GOTPC); default: strcpy(typeName,"unkown type"); } //打印重定位类型、在符号表中的索引、重位内容的地址。 printf("type=%d(%s)\tSYM=%d\t offset=0x%x\n", ELF32_R_TYPE(pRel->r_info),//重定向类型 typeName, ELF32_R_SYM(pRel->r_info),//重定向用到的符号 pRel->r_offset);//需要系统修改的内容地址 #undef TEST_NAME } void printAllRel(char *fileName) { int hFile; int offset,size; Elf32_Ehdr ehdr; Elf32_Shdr shdr; char *strTable; strTable=NULL; hFile=open(fileName,O_RDWR,0); if(hFile<0) { printf("can not open file:%s\n",fileName); return; } if(sizeof(ehdr)!=ReadAt(hFile,0,&ehdr,sizeof(ehdr))) goto error; if(!IsElf(&ehdr))goto error; if(ehdr.e_shnum<=0||ehdr.e_shoff==0) { printf("this ELF have not Section Head Table!\n"); goto close_file; } strTable=(char*)ReadSection(hFile,&ehdr,ehdr.e_shstrndx); if(strTable==NULL) goto error; Elf32_Rel *symName=NULL; Elf32_Rel rel; int i,j; Elf32_Sym sym; //遍历节头表寻找类型为SHT_REL或SHT_RELA的节,它们是重定位表 for(i=0;i<ehdr.e_shnum;i++) { if(sizeof(shdr)==ReadAt(hFile, ehdr.e_shoff+i*ehdr.e_shentsize, &shdr, sizeof(shdr))) { if(shdr.sh_type==SHT_REL||shdr.sh_type==SHT_RELA) { //打印包含重定位表的节名称和节类型 printf("%s(type=%s)\n\t", &strTable[shdr.sh_name], shdr.sh_type==SHT_REL?"SHT_REL":"SHT_RELA"); //打印此节的其它信息 printf("offset=0x%x,\tvaddr=0x%x,\tsize=0x%x\n\n\n", shdr.sh_offset, shdr.sh_addr, shdr.sh_size); //获取重定位表在文件中的偏移,保存在offset变量中 offset=shdr.sh_offset; //循环读取每一个重定位结构 while(offset<shdr.sh_offset+shdr.sh_size) { if(shdr.sh_entsize==ReadAt(hFile,offset,&rel,sizeof(rel))) { //打印此重定位结构的内容 printRel(&rel); } offset+=shdr.sh_entsize; } } } } goto close_file; error: printf("read section info error!\n"); close_file: if(strTable) free(strTable); close(hFile); } |
|
|
关于ELF文件格式的实验
typedef struct { Elf32_Sword d_tag; union { Elf32_Word d_val; Elf32_Addr d_ptr; } d_un; } Elf32_Dyn; typedef struct { Elf64_Sxword d_tag; union { Elf64_Xword d_val; Elf64_Addr d_ptr; } d_un; } Elf64_Dyn; 动态节的类型值是SHT_DYNAMIC。从动态结构的定义,我们可以看出它内容很少,其实只有两个字段。动态节也叫动态表,这样的节的里面保存着一个由动态结构形成的结构数组。尽管动态结构看起来简单,但有许多种类型。它的d_tag字段给出这个结构的类型值,而d_un字段是由d_val来决定还是由d_ptr来决定要看这个结构的类型了。真正使我们简单的是我对除DT_NEEDED类型以外的动态结构只字不提。DT_NEEDED在elf.h中被定义为常量1。对于DT_NEEDED类型的动态结构它的d_val字段是一个字符串表中的索引,它给出的字符串就是这个ELF文件所依赖的外部动态库的名称。你的ELF文件需要依赖多少个动态库就有多少个DT_NEEDED类型的动态结构出现在动态表中。熟悉PE的读者又会发问:DT_NEEDED类型的动态结构给出了程序引用到的外部模块,符号表给出了程序引用的外部函数,可它是怎么告诉系统哪些函数应该到哪个模块中去找呢?ELF中没有这样的信息,我们不能从一个ELF中看出哪个函数属于哪个so文件。与符号的名称类似:保存着动态库名字的字符串表的索引就保存在这个动态节的节头结构中,由sh_link字段给出。我们不再过多讲述动态结构,打印动态结构信息的函数读者可以仿照打印符号表的方式自己写,为省篇幅不再给出。 |
|
|
关于ELF文件格式的实验
如果一个节头结构的sh_type字段为SHT_SYMTAB或SHT_DYNSYM,那么相应的节就是一个符号节,符号节里存放的是一张符号表。符号表也是一个连续存储的结构数组。为了说明方便我把结构的定义从elf.h中拿出来放到下面。什么是符号呢?编程过程中用到的变量和函数都可以称之为符号。一个ELF文件中并不只有一个符号节,通常是两个。一个叫动态符号节,从打印节名的实验中你可以找到名为“.dynsym的”节,它的类型值是SHT_DYNSYM,所有引入的外部符号部在这里有所体现。另一个类型值为SHT_SYMTAB,名字如你所见是“.symtab”,它保存了所有有用的符号的信息。一般我不会依靠节的名称来寻找一个节,虽然它们都在《ELF规范》中有定义。 typedef struct { Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; unsigned char st_info; unsigned char st_other; Elf32_Section st_shndx; } Elf32_Sym; typedef struct { Elf64_Word st_name; unsigned char st_info; unsigned char st_other; Elf64_Section st_shndx; Elf64_Addr st_value; Elf64_Xword st_size; } Elf64_Sym; 我们看到32位与64位下两种结构的定义有了较大的不同,但只是字段出现的序顺改变,我不明白这是为什么!但这不防碍进一步学习每个字段的意义: st_name: 又是一个名字索引。凡是字符串索引必然和一个字符串表相关联,那么与这个字段相关的字符串表在哪里呢?往下看。 st_value: 该字段给出了相应的符号值。根据符号类型和用途的不同它可能是一个内存地址也可能是一个绝对值。 st_size: 符号的字节大小。如果是数据对象可能是该对象占用的字节大小,如果是函数符号则可能是函数体的指令占用字节数。如果符号没有大小或大小不可知此字段为0。 st_info: 这个字段包含两部分。低4位用来表示符号的类型,对于函数这字段的低4位应该等于STT_FUCN;高4位是这个符号的绑定类型,对于从动态库中引入的函数这个字段的高4位应为STB_GLOBAL,表示这个符号是全局符号。在elf.h中,给出了ELF32_ST_TYPE和ELF32_ST_BIND两个宏分别用于获取这个字段的低4位与高4位。相应64位的宏不过是它们的别名。 st_other: 此字段无用,恒为0。 st_shndx: 每个符号都和某些节相关,这个字段给出了一个节头索引。如果函数体所在的节不存于当前文件中,此字段为0。Elf64_Section和Elf32_Section我们头一次遇到,它们都是占两字的整数。 讲到这里我们忍不住提前讨论一下动态连接,动态连接无非两种情况:一是导出;一是引入。如果一个模块想把函数导出给别人使用,它必须能告诉模块的加载者自己能够提供哪些函数、这些函数的函数体在什么位置。ELF的符号表能包含这两条信息。每个符号结构的st_name字段指出了一个符号名称,st_value字段又指出了符号的内存地址(可能需要跟据模块被加载的位置修改)。那么函数导出就轻易解决了。函数的引入相对复杂一些。“显式”引入动态库中的函数,可以通过dlopen、dlsym、dlclose三个在dlfcn.h中定义的函数完美解决,我们并不关心。以我们从其它系统――譬如Windows――获得的经验,若要“隐式”的从一个动态库引入一个函数必须具备两条信息:一是函数名,可以通过这里介绍的符号结构诉操作系统;一是动态库的模块名,后面将要介绍的“动态节”会给出这一信息。事实上大家都知道,Linux上的动态连接是由第三方连动态接器而不是由操作系统解决的,但我们把动态连接器看作操作系统的一部分不去了解。既使如此,Linux上的符号引入没有Windows那么简单。下面给出打印ELF32中所有符号的工具函数,之后我们再介绍动态节与重定位节。 void printSybmol(Elf32_Sym *pSym) { #define TEST_NAME(name)\ case name:\ strcpy(typeName,#name);\ break char typeName[20]; switch(pSym->st_info&0xf) { TEST_NAME(STT_NOTYPE); TEST_NAME(STT_OBJECT); TEST_NAME(STT_FUNC); TEST_NAME(STT_SECTION); TEST_NAME(STT_FILE); default: strcpy(typeName,"unkown type"); } //打印符号类型和属性 printf("type=%d(%s)\tattrib=%d\n", ELF32_ST_TYPE(pSym->st_info),//符号的类型 typeName, ELF32_ST_BIND(pSym->st_info));//绑定类型 //打印其它符号信息 printf("\tst_shndx=%d,\tst_value=0x%x(%d),\tst_size=%d\n", pSym->st_shndx,//与符号相关的节 pSym->st_value,//符号的值 pSym->st_value, pSym->st_size);//符号大小 #undef TEST_NAME } void printAllSymbol(char *fileName) { int hFile; int offset,size; Elf32_Ehdr ehdr; Elf32_Shdr shdr; char *strTable; strTable=NULL; hFile=open(fileName,O_RDWR,0); if(hFile<0) { printf("can not open file:%s\n",fileName); return; } if(sizeof(ehdr)!=ReadAt(hFile,0,&ehdr,sizeof(ehdr))) goto error; if(!IsElf(&ehdr))goto error; if(ehdr.e_shnum<=0||ehdr.e_shoff==0) { printf("this ELF have not Section Head Table!\n"); goto close_file; } //读取节名符号表 strTable=(char*)ReadSection(hFile,&ehdr,ehdr.e_shstrndx); if(strTable==NULL) goto error; char *symName=NULL; int i,j; Elf32_Sym sym; //遍历每个一个节头结构,找出每个保存了符号表的节 for(i=0;i<ehdr.e_shnum;i++) { if(sizeof(shdr)==ReadAt(hFile, ehdr.e_shoff+i*ehdr.e_shentsize, &shdr, sizeof(shdr))) { //如果此节为两种符号节中的一种 if(shdr.sh_type==SHT_SYMTAB||shdr.sh_type==SHT_DYNSYM) { //打印节的名字和节的类型 printf("%s(type=%s)\n\t", &strTable[shdr.sh_name], shdr.sh_type==SHT_SYMTAB?"SHT_SYTAB":"SHT_DYNSYM"); //打印节的其它信息 printf("offset=0x%x,\tvaddr=0x%x,\tsize=0x%x\n\n\n", shdr.sh_offset, shdr.sh_addr, shdr.sh_size); //读取与此符号表相关联的节,这个节是个字符串表,它保存了符号名。 symName=ReadSection(hFile,&ehdr,shdr.sh_link); //获取符号表在文件中的偏移,把它赋值给offset offset=shdr.sh_offset; while(offset<shdr.sh_offset+shdr.sh_size) { //读取一个符号结构 if(shdr.sh_entsize==ReadAt(hFile,offset,&sym,sizeof(sym))) { //打印符号名字 printf("Sym:%s\n\t",symName+sym.st_name); //打印符号其它信息 printSybmol(&sym); } offset+=shdr.sh_entsize; } //释放符号名字符串表 free(symName); } } } goto close_file; error: printf("read section info error!\n"); close_file: if(strTable) free(strTable); close(hFile); } 对上面的函数稍做说明。只有一个参数的printAllSymbol函数将打印出你指定的ELF文件中所有两种类型的符号表内容,在打印每个符号信息的时候它调用了printSybmol。每个符号结构的st_name字段给出了符号名在一个字符串表中的索引。如果仔细阅读上面的代码,你就知道这个保存着符号名的字符串表的索引就保存在这个符号节的节头结构的sh_link字段中! 停下你的思路,做一个实验是有好处的。请调用printAllSymbol函数打印一个你自己的ELF可执行文件,你至少能从它的输出中找到一个名为“main”的符号,而且你将发这个符号存在于类型为SHT_SYTAB的符号表中。仔细观察这个符号,你又发现符号类型为STT_FUNC,这表示“main”是一个函数;符号属性是1,也就是STB_GLOBAL,这表示全局只能有一个“main”函数。另外结合你刚打印的节头表信息,看看st_shndx字段指向了哪个节!同样每个符号表的第一个符号是保留不用的。 |
|
|
关于ELF文件格式的实验
广告内容: hdasm64是我用VC写的Win32软件。 支持16位、32位,64指令集的动态反汇编工具 本身是一个Win32程序(PE32格式)在下载的包里有64位的PE和ELF文件各一个,32位ELF一个,供大家研究! 图形界面。支持实模式、保护模式、64位模式三种模式指令集的反汇编. 支持DOS系统COM和EXE可执行文件格式,支持32位和64位PE文件格式(windows可执行文件),支持32位和64位ELF文件格式(Linux可执行文件),共计六种文件格式。支持部分指令的虚拟执行调试。 下载页面: 7cbK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4m8S2P5e0f1H3x3q4)9J5k6h3y4G2L8g2)9J5c8Y4y4Q4x3V1k6K6y4e0j5#2x3o6c8Q4x3X3g2Z5N6r3@1`. 界面: 1c3K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3k6J5k6h3g2Q4x3X3g2U0L8Y4W2&6M7#2)9J5k6h3y4G2L8g2)9J5c8X3#2&6i4K6u0r3K9r3q4F1k6$3A6Q4x3V1k6#2K9e0u0Q4x3X3g2B7M7r3M7`. hdasm64.exe已被UPX加壳,但你可以把它的复制文件用hdasm64.exe自己打开,然后不断执行单步 (快捷键是F11),用内存窗口观察401000地址开始的内存改变,你会发现它正被解压!! 希望各位能下载试用,帮我发现BUG。先行谢过了!! |
|
|
关于ELF文件格式的实验
用到字符串表的地方很多,也这是我们需要首先了解它的原因! 如果一个节头结构的sh_type字段值为SHT_STRTAB,那么相应的节就是一个字符串表。字符串表保存着一系列以NULL结尾的的字符串,这符合C语言的习惯。值得注意的是字符串表的第一个字节为0,它代表了一个空串。字符串表的最后一个字节也为0,它是表中最后一个字符串的结尾。没有任何字节的字符串表也允许存在这时对字符串的引用是无效的。字符串表试图节省空间,所以字符串的子串也会被引用。表中的字符串是怎样被具体引用的呢?获取节的名称就是我们说明如何使用字符串表的第一个例子。节头结构的sh_name字段是个两字节的整数,这个整数是从字符串表第一个字节开始的偏移值。所以如果我们已经拿到了一个字符串表,那么从它的第sh_name个字节开始直到遇到一个0结束,之间所有的字符构成了我们所要的字符串。看一下ELF头的最后一个字段e_shstrndx,还记得对它的解释吗?对,它指出的那个节就是一个字符串表,文件中各个节的名字都存在这里!ELF中用到字符串表的地方给出的都是一个字符串表在节头表中的索引,就象e_shstrndx,我们看一下如何使用。 下面给出一些C函数,它们将以ELF32为例打印文件中每一个节的名称和其它信息。此处未给出的函数,都在“覆盖ELF可执行文件入口指令”实验中给出过,为省篇幅不再重复给出。为了方便我们先写一个辅助函数ReadSection,这个函数接受的头两个参数是ELF的文件句柄和ELF头结构的指针,第三个参数是需要读取的节在节头中的索引。如果读取成功它将用malloc分配内存并返回这块内存的指针,调用者负责释放内存。真正打印节名和其它节信息的是函数printSection,这个函数接受一个ELF的文件名做为参数。读者可自行编写main函数来调用printSection。后面每讲到一种节,我都会给出相应的函数用于打印这个些节中包含的内容。一方面是为了更好的了解这些节存储信息的方式,一方面也是为后续的学习积累工具。 char *ReadSection(int hFile,Elf32_Ehdr*pEhdr,int index) { char *pbuf; Elf32_Shdr shdr; int offset; if(index<0||index>pEhdr->e_shnum) return NULL; //相应的节头结构在文件中的偏移=节头偏移+每个节头结构的字节大小×节索引 offset=pEhdr->e_shoff+pEhdr->e_shentsize*index; //从文件中读取一个节头结构 if(sizeof(shdr)!=ReadAt(hFile,offset,&shdr,sizeof(shdr))) return NULL; //分配与节字节大小相同的内存块 pbuf=(char *)malloc(shdr.sh_size); if(pbuf!=NULL) { //把该节全部读入内存,成功就返回内存地址,否则释放内存返回NULL if(shdr.sh_size==ReadAt(hFile,shdr.sh_offset,pbuf,shdr.sh_size)) return pbuf; free(pbuf); } return NULL; } void printSection(char *fileNameOfElf) { int hFile; int offset; Elf32_Ehdr ehdr; Elf32_Shdr shdr; char *strTable; strTable=NULL; //以只读方式打开ELF文件 hFile=open(fileNameOfElf,O_RDONLY,0); if(hFile<0) { printf("can not open file:%s\n",fileName); return; } //读取ELF头 if(sizeof(ehdr)!=ReadAt(hFile,0,&ehdr,sizeof(ehdr))) goto error; //判断是否是ELF文件 if(!IsElf(&ehdr))goto error; //判断节头是否存在 if(ehdr.e_shnum<=0||ehdr.e_shoff==0) { printf("this ELF have not Section Head Table!\n"); goto close_file; } //把字符串表读到内存中,strTable保存了字符串表在内存中的地址 strTable=ReadSection(hFile,&ehdr,ehdr.e_shstrndx); if(strTable==NULL) goto error; int i; //循环读取每一个节头结构,打印出节的信息 for(i=0;i<ehdr.e_shnum;i++) { //读取第i个节头结构 if(sizeof(shdr)==ReadAt(hFile, ehdr.e_shoff+i*ehdr.e_shentsize, &shdr, sizeof(shdr))) { //调用printf打印包括节名在内的节信息 printf("section name=%s\n" "\tfstart=0x%x,\tfsize=0x%x,\tmemstart=0x%x\n", &strTable[shdr.sh_name],//节名字符串地址 shdr.sh_offset, //节在文件中的偏移 shdr.sh_size, //节的字节大小 shdr.sh_addr); //节在内存中的地址 } } goto close_file; error: printf("read section info error!\n"); close_file: if(strTable) free(strTable);//释放内存 close(hFile);//释放文件句柄 } 我的函数写得很笨拙,但请读者把注意力放到如何读取一个节和如何用索引来查找字符串上。请你调用printSection函数,你会从这个实验结果中发现:你的ELF文件的第一个节没有名字,而且这个节的其它信息都为0。是的每个ELF文件的头一个节都被保留不用。 |
操作理由
RANk
{{ user_info.golds == '' ? 0 : user_info.golds }}
雪币
{{ experience }}
课程经验
{{ score }}
学习收益
{{study_duration_fmt}}
学习时长
基本信息
荣誉称号:
{{ honorary_title }}
勋章
兑换勋章
证书
证书查询 >
能力值