我正在尝试了解一些有关二进制利用的知识,并且正在构建一个最小的沙箱供我进行实验,但我遇到了一个奇怪的问题。考虑这个 main.c:
// cat > main.c ; make main
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>
#include <unistd.h>
int main()
{
void (*f)(void);
char * buf = mmap (0, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
int fd = socket(AF_INET, SOCK_STREAM, 0);
struct addrinfo* sa = malloc(sizeof(struct addrinfo));
sa->ai_family = AF_INET;
sa->ai_flags = 0;
sa->ai_socktype = SOCK_STREAM;
sa->ai_protocol = 0;
int ret = getaddrinfo("localhost", "31337", NULL, &sa);
// printf("getaddrinfo: %d\n", ret);
sa->ai_addr->sa_family = AF_INET;
ret = connect(fd, sa->ai_addr, sa->ai_addrlen);
// printf("connect: %d\n", ret);
// perror("connect");
read(fd, buf, sizeof(buf));
// printf("buf: %s\n", buf);
f = buf;
f();
}
还有这个 elo.asm:
; cat > elo.asm ;
bits 64
xor eax, eax
inc eax
mov bh, 4
int 0x80
我跑
ncat -k -l --sh-exec "nasm elo.asm -o /dev/stdout"
,然后./main
。这可以工作并以看似随机的退出代码退出。当我用 mov bh, 4
替换 mov ebx, 4
时,问题就出现了。这会产生 Segmentation fault
,后跟以下 dmesg
消息:
[20096.076031] main[60973]: segfault at 1 ip 00007f6a8ad4f009 sp 00007ffcc6bce518 error 6 likely on CPU 4 (core 4, socket 0)
[20096.076039] Code: Unable to access opcode bytes at 0x7f6a8ad4efdf.
那是什么?我该如何诊断?我缺少一些填充吗?
问题是我试图读取 sizeof(buf),这是我在更改 buf 的构造方式时不小心留下的东西。除了更好的错误检查之外,这里还有代码的工作版本:
/*
This program demonstrates a simple example of a remote code execution
vulnerability. The program connects to a remote server, reads a buffer from
the server and executes it.
To test it, create the following elo.asm file:
bits 64 ; 64-bit mode
xor eax, eax ; zero out eax
inc eax ; eax = 1
mov ebx, 4 ; syscall number for sys_exit
int 0x80 ; call sys_exit
Then set up a server that serves the shellcode, for example:
ncat -p 31337 -k -l --sh-exec "nasm elo.asm -o /dev/stdout"
Then run the program and it will execute the shellcode.
WARNING: This program is vulnerable to remote code execution and should not be
used in production. It is only for educational purposes.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#define BUF_SIZE 4096
int main()
{
// Let's allocate a buffer with read, write and execute permissions.
// This way we can write the shellcode to the buffer and execute it.
// We explicitly set PROT_EXEC to allow the buffer to be executed,
// bypassing W^X protection.
char * buf = mmap (0, BUF_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (buf == MAP_FAILED) {
perror("mmap");
exit(1);
}
// Create a socket. This can fail if e.g. the system is out of file
// descriptors.
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
perror("socket");
exit(1);
}
// Allocate a struct addrinfo and set the fields to connect to the
// server. This can fail if e.g. the system is out of memory.
struct addrinfo* sa = malloc(sizeof(struct addrinfo));
if (sa == NULL) {
perror("malloc");
exit(1);
}
sa->ai_family = AF_INET;
sa->ai_flags = 0;
sa->ai_socktype = SOCK_STREAM;
sa->ai_protocol = 0;
sa->ai_addr->sa_family = AF_INET;
// Resolve the address of the server. This can fail if e.g. the address
// is not found.
int ret = getaddrinfo("localhost", "31337", NULL, &sa);
if (ret != 0) {
perror("getaddrinfo");
exit(1);
}
// Connect to the server. This can fail if e.g. the server is not
// reachable.
ret = connect(fd, sa->ai_addr, sa->ai_addrlen);
if (ret == -1) {
perror("connect");
exit(1);
}
// Read the shellcode from the server. This can fail if e.g. the server
// disconnects.
ret = read(fd, buf, BUF_SIZE);
if (ret == -1) {
perror("read");
exit(1);
}
// Execute the shellcode. We cast the buffer to a function pointer and
// call it. If this fails, the shellcode is likely invalid.
void (*f)(void);
f = buf;
f();
}