我必须做什么才能在数据区执行代码,(段保护)
What do I have to do to execute code in data areas, ( segment protection )
我在 linux 平台上工作,我将 g++ 与上述程序一起使用,将函数从代码区域复制到数据区域。如何更改数据段的保护以允许我执行复制的函数?
代码如下:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#define Return asm volatile("pop %rbp; retq; retq; retq; retq; retq;")
int64_t funcEnd=0xc35dc3c3c3c3c35d;
constexpr int maxCode=0x800;
int8_t code[maxCode];
void testCode(void){
int a=8,b=7;
a+=b*a;
Return;
}
typedef void (*action)(void);
int main(int argc, char **argv)
{
action a=&testCode;
testCode();
int8_t *p0=(int8_t*)a,*p=p0,*p1=p0+maxCode;
for(;p!=p1;p++)
if ( (*(int64_t*)p)==funcEnd ) break;
if(p!=p1){
p+=sizeof(int64_t);
printf("found\n");
memcpy(&code,(void*)a,p-(int8_t*)a);
((action)&code)();
}
printf("returning 0\n");
return 0;
}
这取决于您是尝试静态(在构建时)还是动态(在 运行 时)执行此操作。
构建时间
您需要告诉 GCC 将您的 blob 放在可执行的部分中。我们在创建时使用__attribute__((section))
和this trick指定节的属性。
运行-次
TL;DR:跳转到我的答案的末尾,我在那里使用 mmap
.
尽管其他人可能会质疑为什么您要在 运行 时允许这样的事情,但请记住,这 正是 虚拟机具有JIT 编译器(例如 Java VM、.NET CLR 等)在发出本机代码时执行。
您需要更改您尝试执行的内存的内存保护。我们用 mprotect(addr, PROT_EXEC)
来做到这一点。请注意,addr
必须与您平台的页面大小对齐。在 x86 上,页面大小为 4K。我们使用 aligned_alloc
来保证这种对齐方式。
(两者的)示例:
#define _ISOC11_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h> /* mprotect() */
__attribute__((section(".my_executable_blob,\"awx\",@progbits#")))
static uint8_t code[] = {
0xB8,0x2A,0x00,0x00,0x00, /* mov eax,0x2a */
0xC3, /* ret */
};
int main(void)
{
int (*func)(void);
/* Execute a static blob of data */
func = (void*)code;
printf("(static) code returned %d\n", func());
/* Execute a dynamically-allocated blob of data */
void *p = aligned_alloc(0x1000, sizeof(code));
if (!p) {
fprintf(stderr, "aligned_alloc() failed\n");
return 2;
}
memcpy(p, code, sizeof(code));
if (mprotect(p, sizeof(code), PROT_EXEC) < 0) {
perror("mprotect");
return 2;
}
func = p;
printf("(dynamic) code returned %d\n", func());
return 0;
}
输出:
$ ./a.out
(static) code returned 42
(dynamic) code returned 42
SELinux 影响
请注意,这会将您的可执行代码 放在堆 上,这可能有点危险。我的 CentOS 7 机器上的 SELinux 实际上拒绝了 mprotect
调用:
SELinux is preventing /home/jreinhart/so/a.out from using the execheap access on a process.
***** Plugin allow_execheap (53.1 confidence) suggests ********************
If you do not think /home/jreinhart/so/a.out should need to map heap memory that is both writable and executable.
Then you need to report a bug. This is a potentially dangerous access.
所以我不得不暂时 sudo setenforce 0
让它工作。
我不确定为什么,但是,因为在 /proc/[pid]/maps
中查看,页面清楚地标记为仅可执行,而不是 SELinux 指示的 "writable and executable"。如果我将 memcpy
移动到 mprotect
之后,我的进程就会出现段错误,因为我正在尝试写入不可写内存。所以看起来 SELinux 在这里有点过于热心了。
改用mmap
与 mprotect
堆的一个区域(使用 aligned_alloc
分配)不同,使用 mmap
更直接。这也避免了 SELinux 的任何问题,因为我们没有尝试在堆上执行。
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h> /* mmap() */
static uint8_t code[] = {
0xB8,0x2A,0x00,0x00,0x00, /* mov eax,0x2a */
0xC3, /* ret */
};
int main(void)
{
void *p = mmap(NULL, sizeof(code), PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (p==MAP_FAILED) {
fprintf(stderr, "mmap() failed\n");
return 2;
}
memcpy(p, code, sizeof(code));
int (*func)(void) = p;
printf("(dynamic) code returned %d\n", func());
pause();
return 0;
}
最终解决方案
mmap
解决方案很好,但它没有为我们提供任何安全;我们的 mmap
ed 代码区域是可读、可写和可执行的。最好在我们将代码放置到位时只允许内存可写,然后使其仅可执行。以下代码就是这样做的:
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h> /* mmap(), mprotect() */
static uint8_t code[] = {
0xB8,0x2A,0x00,0x00,0x00, /* mov eax,0x2a */
0xC3, /* ret */
};
int main(void)
{
const size_t len = sizeof(code);
/* mmap a region for our code */
void *p = mmap(NULL, len, PROT_READ|PROT_WRITE, /* No PROT_EXEC */
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (p==MAP_FAILED) {
fprintf(stderr, "mmap() failed\n");
return 2;
}
/* Copy it in (still not executable) */
memcpy(p, code, len);
/* Now make it execute-only */
if (mprotect(p, len, PROT_EXEC) < 0) {
fprintf(stderr, "mprotect failed to mark exec-only\n");
return 2;
}
/* Go! */
int (*func)(void) = p;
printf("(dynamic) code returned %d\n", func());
pause();
return 0;
}
我在 linux 平台上工作,我将 g++ 与上述程序一起使用,将函数从代码区域复制到数据区域。如何更改数据段的保护以允许我执行复制的函数?
代码如下:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#define Return asm volatile("pop %rbp; retq; retq; retq; retq; retq;")
int64_t funcEnd=0xc35dc3c3c3c3c35d;
constexpr int maxCode=0x800;
int8_t code[maxCode];
void testCode(void){
int a=8,b=7;
a+=b*a;
Return;
}
typedef void (*action)(void);
int main(int argc, char **argv)
{
action a=&testCode;
testCode();
int8_t *p0=(int8_t*)a,*p=p0,*p1=p0+maxCode;
for(;p!=p1;p++)
if ( (*(int64_t*)p)==funcEnd ) break;
if(p!=p1){
p+=sizeof(int64_t);
printf("found\n");
memcpy(&code,(void*)a,p-(int8_t*)a);
((action)&code)();
}
printf("returning 0\n");
return 0;
}
这取决于您是尝试静态(在构建时)还是动态(在 运行 时)执行此操作。
构建时间
您需要告诉 GCC 将您的 blob 放在可执行的部分中。我们在创建时使用__attribute__((section))
和this trick指定节的属性。
运行-次
TL;DR:跳转到我的答案的末尾,我在那里使用 mmap
.
尽管其他人可能会质疑为什么您要在 运行 时允许这样的事情,但请记住,这 正是 虚拟机具有JIT 编译器(例如 Java VM、.NET CLR 等)在发出本机代码时执行。
您需要更改您尝试执行的内存的内存保护。我们用 mprotect(addr, PROT_EXEC)
来做到这一点。请注意,addr
必须与您平台的页面大小对齐。在 x86 上,页面大小为 4K。我们使用 aligned_alloc
来保证这种对齐方式。
(两者的)示例:
#define _ISOC11_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h> /* mprotect() */
__attribute__((section(".my_executable_blob,\"awx\",@progbits#")))
static uint8_t code[] = {
0xB8,0x2A,0x00,0x00,0x00, /* mov eax,0x2a */
0xC3, /* ret */
};
int main(void)
{
int (*func)(void);
/* Execute a static blob of data */
func = (void*)code;
printf("(static) code returned %d\n", func());
/* Execute a dynamically-allocated blob of data */
void *p = aligned_alloc(0x1000, sizeof(code));
if (!p) {
fprintf(stderr, "aligned_alloc() failed\n");
return 2;
}
memcpy(p, code, sizeof(code));
if (mprotect(p, sizeof(code), PROT_EXEC) < 0) {
perror("mprotect");
return 2;
}
func = p;
printf("(dynamic) code returned %d\n", func());
return 0;
}
输出:
$ ./a.out
(static) code returned 42
(dynamic) code returned 42
SELinux 影响
请注意,这会将您的可执行代码 放在堆 上,这可能有点危险。我的 CentOS 7 机器上的 SELinux 实际上拒绝了 mprotect
调用:
SELinux is preventing /home/jreinhart/so/a.out from using the execheap access on a process.
***** Plugin allow_execheap (53.1 confidence) suggests ********************
If you do not think /home/jreinhart/so/a.out should need to map heap memory that is both writable and executable.
Then you need to report a bug. This is a potentially dangerous access.
所以我不得不暂时 sudo setenforce 0
让它工作。
我不确定为什么,但是,因为在 /proc/[pid]/maps
中查看,页面清楚地标记为仅可执行,而不是 SELinux 指示的 "writable and executable"。如果我将 memcpy
移动到 mprotect
之后,我的进程就会出现段错误,因为我正在尝试写入不可写内存。所以看起来 SELinux 在这里有点过于热心了。
改用mmap
与 mprotect
堆的一个区域(使用 aligned_alloc
分配)不同,使用 mmap
更直接。这也避免了 SELinux 的任何问题,因为我们没有尝试在堆上执行。
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h> /* mmap() */
static uint8_t code[] = {
0xB8,0x2A,0x00,0x00,0x00, /* mov eax,0x2a */
0xC3, /* ret */
};
int main(void)
{
void *p = mmap(NULL, sizeof(code), PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (p==MAP_FAILED) {
fprintf(stderr, "mmap() failed\n");
return 2;
}
memcpy(p, code, sizeof(code));
int (*func)(void) = p;
printf("(dynamic) code returned %d\n", func());
pause();
return 0;
}
最终解决方案
mmap
解决方案很好,但它没有为我们提供任何安全;我们的 mmap
ed 代码区域是可读、可写和可执行的。最好在我们将代码放置到位时只允许内存可写,然后使其仅可执行。以下代码就是这样做的:
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h> /* mmap(), mprotect() */
static uint8_t code[] = {
0xB8,0x2A,0x00,0x00,0x00, /* mov eax,0x2a */
0xC3, /* ret */
};
int main(void)
{
const size_t len = sizeof(code);
/* mmap a region for our code */
void *p = mmap(NULL, len, PROT_READ|PROT_WRITE, /* No PROT_EXEC */
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (p==MAP_FAILED) {
fprintf(stderr, "mmap() failed\n");
return 2;
}
/* Copy it in (still not executable) */
memcpy(p, code, len);
/* Now make it execute-only */
if (mprotect(p, len, PROT_EXEC) < 0) {
fprintf(stderr, "mprotect failed to mark exec-only\n");
return 2;
}
/* Go! */
int (*func)(void) = p;
printf("(dynamic) code returned %d\n", func());
pause();
return 0;
}