24.1 如何使代码段可写 Q: 如下演示程序试图对代码段进行写操作,缺省情况下必然失败,有何建议。 -------------------------------------------------------------------------- /* * ----------------------------------------------------------------------- * Compile : For x86/Linux RedHat/7.2 2.4.7-10(gcc 2.96/gas 2.11.90.0.8) * : gcc -static -Wall -pipe -g -o src src.c * ----------------------------------------------------------------------- */ #include int main ( int argc, char * argv[] ) { unsigned int *p; p = ( unsigned int * )&main; printf( "[0x%08X] -> 0x%08X\n", ( unsigned int )p, *p ); *p = 0x4F46534E; printf( "[0x%08X] -> 0x%08X\n", ( unsigned int )p, *p ); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ ./src [0x080481E0] -> 0x83E58955 Segmentation fault (core dumped) $ A: scz 无论是哪种Unix系统,总可以利用mprotect()设置PC附近的内存权限为rwx: -------------------------------------------------------------------------- /* * ----------------------------------------------------------------------- * Compile : For x86/Linux RedHat/7.2 2.4.7-10(gcc 2.96/gas 2.11.90.0.8) * : gcc -static -Wall -pipe -g -o src_other src_other.c * ----------------------------------------------------------------------- */ #include #include int main ( int argc, char * argv[] ) { unsigned int *p = ( unsigned int * )( ( unsigned int )&main & 0xffffc000 ); if ( mprotect( p, 0x4000, 7 ) < 0 ) { perror( "mprotect error" ); return( -1 ); } p = ( unsigned int * )&main; printf( "[0x%08X] -> 0x%08X\n", ( unsigned int )p, *p ); *p = 0x4F46534E; printf( "[0x%08X] -> 0x%08X\n", ( unsigned int )p, *p ); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ ./src_other [0x080481E0] -> 0x83E58955 [0x080481E0] -> 0x4F46534E 修改静态文件中代码段的p_flags,从(PF_R | PF_X)改为(PF_R | PF_W | PF_X),这 样的ELF文件加载后,代码段缺省可写。下面是一个简单的跨平台可移植演示程序: -------------------------------------------------------------------------- /* * ----------------------------------------------------------------------- * Compile : For x86/Linux RedHat_8 2.4.18-14 * : For x86/FreeBSD 4.5-RELEASE * : For SPARC/Solaris 8 * : gcc -Wall -pipe -O3 -o codew codew.c * ----------------------------------------------------------------------- */ #include #include #include #include #include #include #include #define PT_LOAD 1 /* Loadable program segment */ #define PF_X (1 << 0) /* Segment is executable */ #define PF_W (1 << 1) /* Segment is writable */ #define PF_R (1 << 2) /* Segment is readable */ /* * The ELF file header. This appears at the start of every ELF file. */ struct ELF32EH { unsigned char e_ident[16]; /* Magic number and other info */ unsigned short int e_type; /* Object file type */ unsigned short int e_machine; /* Architecture */ unsigned int e_version; /* Object file version */ unsigned int e_entry; /* Entry point virtual address */ unsigned int e_phoff; /* Program header table file offset */ unsigned int e_shoff; /* Section header table file offset */ unsigned int e_flags; /* Processor-specific flags */ unsigned short int e_ehsize; /* ELF header size in bytes */ unsigned short int e_phentsize; /* Program header table entry size */ unsigned short int e_phnum; /* Program header table entry count */ unsigned short int e_shentsize; /* Section header table entry size */ unsigned short int e_shnum; /* Section header table entry count */ unsigned short int e_shstrndx; /* Section header string table index */ } __attribute__ ((packed)); struct ELF32PH { unsigned int p_type; /* Segment type */ unsigned int p_offset; /* Segment file offset */ unsigned int p_vaddr; /* Segment virtual address */ unsigned int p_paddr; /* Segment physical address */ unsigned int p_filesz; /* Segment size in file */ unsigned int p_memsz; /* Segment size in memory */ unsigned int p_flags; /* Segment flags */ unsigned int p_align; /* Segment alignment */ } __attribute__ ((packed)); int main ( int argc, char * argv[] ) { struct ELF32EH eh; struct ELF32PH ph; unsigned short int e_phnum; int fd = -1; if ( argc != 2 ) { fprintf( stderr, "Usage: %s <32-bit ELF file>\n", argv[0] ); return( EXIT_FAILURE ); } if ( ( fd = open( argv[1], O_RDWR ) ) < 0 ) { perror( "open error" ); return( EXIT_FAILURE ); } if ( read( fd, &eh, sizeof( eh ) ) != sizeof( eh ) ) { printf( "read eh error\n" ); goto main_0; } for ( e_phnum = 0; e_phnum < eh.e_phnum; e_phnum++ ) { if ( lseek( fd, eh.e_phoff + e_phnum * sizeof( ph ), SEEK_SET ) < 0 ) { perror( "lseek error" ); goto main_0; } if ( read( fd, &ph, sizeof( ph ) ) != sizeof( ph ) ) { printf( "read ph error\n" ); goto main_0; } if ( ( ph.p_type == PT_LOAD ) && ( ( ph.p_flags & ( PF_R | PF_X ) ) == ( PF_R | PF_X ) ) ) { printf( "old ph.p_flags = 0x%08x\n", ph.p_flags ); ph.p_flags |= PF_W; printf( "new ph.p_flags = 0x%08x\n", ph.p_flags ); lseek( fd, eh.e_phoff + e_phnum * sizeof( ph ), SEEK_SET ); if ( write( fd, &ph, sizeof( ph ) ) != sizeof( ph ) ) { printf( "write ph error\n" ); goto main_0; } break; } } /* end of for */ main_0: close( fd ); fd = -1; return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- 下面在SPARC/Solaris 8上测试效果: $ ./src [0x0001080C] -> 0x9DE3BF88 段错误 (core dumped) $ elfdump -p src 程序头[2]: p_vaddr: 0x10000 p_flags: [ PF_X PF_R ] p_paddr: 0 p_type: [ PT_LOAD ] p_filesz: 0x934 p_memsz: 0x934 p_offset: 0 p_align: 0x10000 $ ./codew src old ph.p_flags = 0x00000005 new ph.p_flags = 0x00000007 $ elfdump -p src 程序头[2]: p_vaddr: 0x10000 p_flags: [ PF_X PF_W PF_R ] p_paddr: 0 p_type: [ PT_LOAD ] p_filesz: 0x934 p_memsz: 0x934 p_offset: 0 p_align: 0x10000 $ ./src [0x0001080C] -> 0x9DE3BF88 [0x0001080C] -> 0x4F46534E 对于Linux、FreeBSD有readelf工具可用: $ readelf -l src Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x000000 0x08048000 0x08048000 0x00533 0x00533 R E 0x1000 $ ./codew src old ph.p_flags = 0x00000005 new ph.p_flags = 0x00000007 $ readelf -l src Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x000000 0x08048000 0x08048000 0x00533 0x00533 RWE 0x1000 $ 用"objdump -x src | more"也能看到这些信息。 "law@bbs.apue.net"曾经建议过这样的命令: $ ./src [0x08048494] -> 0x83E58955 Bus error (core dumped) $ objdump -h src Sections: Idx Name Size VMA LMA File off Algn 8 .text 00000190 08048388 08048388 00000388 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE $ objcopy --set-section-flags .text=CONTENTS,ALLOC,LOAD,CODE src dst $ objdump -h dst Idx Name Size VMA LMA File off Algn 8 .text 00000190 08048388 08048388 00000388 2**2 CONTENTS, ALLOC, LOAD, CODE $ ./dst [0x08048494] -> 0x83E58955 Bus error (core dumped) $ 这条objcopy命令修改了.text的sh_flags,而不是全局的p_flags,去掉READONLY也 未能使得代码段缺省可写。 "watercloud@nsfocus.com"建议过另一种邪门办法,下面在SPARC/Solaris 8上演示: $ gcc -Wall -S -o src.s src.c 编辑src.s文件,将main()所在的节名由.text改成.data,继续编译: $ gcc -static -Wall -pipe -g -o src src.s $ ./src [0x0004E770] -> 0x9DE3BF88 [0x0004E770] -> 0x4F46534E $ elfdump -p src 程序头[0]: p_vaddr: 0x10078 p_flags: [ PF_X PF_R ] p_paddr: 0 p_type: [ PT_LOAD ] p_filesz: 0x2e67e p_memsz: 0x2e67e p_offset: 0x78 p_align: 0x10000 程序头[1]: p_vaddr: 0x4e6f8 p_flags: [ PF_X PF_W PF_R ] p_paddr: 0 p_type: [ PT_LOAD ] p_filesz: 0x1a31 p_memsz: 0x2640 p_offset: 0x2e6f8 p_align: 0x10000 $