How to Implement Download Functionality in Linux Without curl and wget Using Shell
Previously, we analyzed how to download files in Linux without using curl and wget by using exec to specify /dev/tcp/ip/port. However, in container scenarios, there are no pseudo-device files like /dev/tcp. So, how can we achieve remote downloading for a Linux file download?
This article uses the Alpine image as an example. Alpine, being the most lightweight container image, is only about 5MB. This means many common commands, including curl and wget, are not available, and the container does not have the /dev/tcp device, making the exec trick unusable.
Scenario Analysis
In offensive and defensive scenarios, we usually gain access to the container, possibly through command execution or a reverse shell. However, the container lacks any tools for downloading, and we cannot install commands via apk.
How to Determine the Base Image of a Container?
cat /etc/os-release

/etc/os-release can be used to determine the base image of the container.
Countermeasure Strategy
Since we cannot install download command tools within the container, we can manually write a pre-compiled download tool into the container.
Here, I implemented a simple file download tool in C that only supports the HTTP protocol.
#include <arpa/inet.h> #include #include #include <netinet/in.h> #include #include #include #include <sys/socket.h> #include <sys/types.h> #include int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, " C Downloader \n\n Usage: %s \n only support http protocol \n", argv[0]); return 1; } 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) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); return 1; } for (p = servinfo; p != NULL; p = p-> ai_next) { if ((sockfd = socket(p-> ai_family, p-> ai_socktype, p-> ai_protocol)) == -1) { perror("socket"); continue; } if (connect(sockfd, p-> ai_addr, p-> ai_addrlen) == -1) { close(sockfd); perror("connect"); continue; } break; } if (p == NULL) { fprintf(stderr, "failed to connect\n"); 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; }
Pitfalls of Compiling the Download Tool
We need to compile the above C code, but we must note that the dynamic link libraries on the host and in the Alpine container are different.
Here, we use gcc to compile the same C code.
gcc -Os -s -fdata-sections -ffunction-sections -flto -Wl,--gc-sections -o downloader downloader.c

Therefore, programs compiled on the host cannot run in the container and need to be recompiled within the container!
Since Alpine Linux uses musl libc as the default C library, programs compiled in the Alpine environment will automatically link to musl libc.
To compile a binary that depends on musl libc on Alpine Linux, you need to use an Alpine Linux environment and ensure the necessary compilation tools are installed.
Compilation Process
docker run --name alpine -dit alpine // Enter the container docker exec -it alpine sh // Install gcc apk add --no-cache build-base // Compile the tool code gcc -Os -s -fdata-sections -ffunction-sections -flto -Wl,--gc-sections -o downloader downloader.c
gzip Compression of Shellcode
The compiled binary file is usually several kilobytes, which, although not large, can still affect our transmission. We can use gzip to compress it to the smallest size.

After gzip compression, the length is acceptable. Here, I have placed the compressed shellcode below.
/ # cat output | base64 -d > new.gz / # gzip -d new.gz / # chmod +x new / # ./new C Downloader Usage: ./new only support http protocol

Supplement
Some Alpine images actually come with busybox. If you have it, there’s no need to go through all the trouble. You can directly use curl or wget from busybox to download the file.
Post Views: 814