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 <stdio.h>
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 );
$ ./src [0x080481E0] -> 0x83E58955 Segmentation fault (core dumped) $
A: scz [email protected]
无论是哪种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 <stdio.h> #include <sys/mman.h>
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 );
$ ./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 <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>
#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 );
下面在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"也能看到这些信息。
"[email protected]"曾经建议过这样的命令:
$ ./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 22 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 22 CONTENTS, ALLOC, LOAD, CODE $ ./dst [0x08048494] -> 0x83E58955 Bus error (core dumped) $
这条objcopy命令修改了.text的sh_flags,而不是全局的p_flags,去掉READONLY也 未能使得代码段缺省可写。
"[email protected]"建议过另一种邪门办法,下面在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 $