ALERT!
Click here to register with a few steps and explore all our cool stuff we have to offer!
C/C++

C++Network Share Scanner (Windows 10/11)

Submitted by Remio at 01-05-2025, 10:00 AM


C++Network Share Scanner (Windows 10/11)
412 Views
Remio's Avatar'
Remio
Offline
#1
Network Share Scanner with nscan (Windows C++) — Introduction

FYI: Source code at bottom of post.

Hello again fellow sinister.ly browser. Following on from my previous posts I thought you may perhaps be interested in some network enumeration techniques? This tutorial introduces a `nscan`, a C++ utility for discovering and processing network shares (SMB, DFS, mapped drives) on Windows, designed to be easily added or integrated into your project. We'll explore its design and core techniques, in line with my previous style but a little more in depth - perfect for developers and beginners alike.

This file uses only native Windows APIs (netapi32.dll, iphlpapi.dll, wininet.dll) to:
- Enumerate local subnets and active hosts;
- List accessible shares with >100MB free space;
- Process shares via a custom callback.
- Persist scan state for resumption.

Program Overview:
- Scans subnets for hosts via IPC$ connections.
- Enumerates disk shares, skipping ADMIN$, C$, IPC$.
- Processes shares with sufficient disk space.
- Saves state to nscan_state.dat for crash recovery.
- Single-threaded, optimized for Windows 10/11.

Key Features and Techniques:

1. Subnet Enumeration
- Uses GetIpNetTable to list private IP subnets (e.g., 192.168.0.0/16).
- Falls back to loopback (127.0.0.0) if no subnets found:
Code:
std::vector<ULONG> GetLocalSubnets() {
    PMIB_IPNETTABLE ipNetTable = (PMIB_IPNETTABLE)malloc(tableSize);
    if (GetIpNetTable(ipNetTable, &tableSize, FALSE) == NO_ERROR) {
        for (ULONG i = 0; i < ipNetTable->dwNumEntries; i++) {
            if (/* Private IP check */) subnetList.push_back(address & 0xFFFFFF00);
        }
    }
    if (subnetList.empty()) subnetList.push_back(0x7F000000);
    return subnetList;
}

2. Randomized Host Scanning
- Tests up to 30 hosts per subnet (500ms timeout) by connecting to \\<IP>\IPC$.
- Randomizes scan order to avoid detection:
Code:
std::vector<ULONG> ScanSubnet(ULONG subnet) {
    std::vector<int> addressList(254);
    std::shuffle(addressList.begin(), addressList.end(), std::mt19937(std::random_device()()));
    for (int i : addressList) {
        if (hosts.size() >= 30 || GetTickCount() - startTime > 500) break;
        if (TestHostConnection((subnet & 0xFFFFFF00) | i, nullptr)) hosts.push_back(hostAddr);
    }
    return hosts;
}

3. Share Enumeration
- Uses NetShareEnum to list disk shares, excluding administrative shares:
Code:
void EnumShares(ULONG hostAddress, SHARE_LIST* shares) {
    NET_API_STATUS status = NetShareEnum(hostStr, 1, (LPBYTE*)&pShareInfo, …);
    if (status == ERROR_SUCCESS) {
        for (DWORD i = 0; i < entriesRead; i++) {
            if (pShareInfo[i].shi1_type == STYPE_DISKTREE && wcscmp(pShareInfo[i].shi1_netname, L"ADMIN$") != 0) {
                /* Add share to list */
            }
        }
    }
}

4. Disk Space Check
- Ensures shares have >100MB free using GetDiskFreeSpaceExW:
Code:
bool HasSufficientDiskSpace(const WCHAR* path) {
    ULARGE_INTEGER freeBytesAvailable;
    return GetDiskFreeSpaceExW(volumePath, &freeBytesAvailable, nullptr, nullptr) && freeBytesAvailable.QuadPart >= 104857600;
}

5. Custom Callback
- Allows custom processing of shares via a callback function:
Code:
typedef void (*SHARE_CALLBACK)(const WCHAR*);
int ScanNetwork(SHARE_CALLBACK callback, BYTE* pSharedKey, DWORD sharedKeyLen) {
    /* Invoke callback for each valid share */
}

6. State Persistence
- Saves scan progress to nscan_state.dat for resumption:
Code:
void SaveScanState() {
    std::ofstream stateFile(L”nscan_state.dat”, std::ios::binary);
    stateFile.write((const char*)&g_scanState, sizeof(ScanState));
}

7. Timeout Controls
- Enforces 1500ms total scan, 500ms per subnet, 300ms per share enumeration.

Security Considerations:
Strengths:
- Efficient, lightweight (just standard Windows APIs (netapi32.dll, iphlpapi.dll, wininet.dll).
- Resilient to crashes via state persistence.
- Extensible via callback.
Weaknesses:
- Single-threaded; no concurrent scanning in this implimentation.
- No authentication for restricted shares.
- Pretty basic error handling.
- Obvious lack of obfuscation for readability
Future Improvements:
- Add multithreading for faster scans,
- Support credential-based share access.
- Enhance error logging.

Summary:
This file does as it is supposed to. Demonstrates network scanning using Windows APIs, and like many of other posts - is ideal for learning subnet enumeration, share discovery, and state management. It’s a solid foundation for getting into writing real-world custom tools.

- Remy

nscan.cpp full
Code:
#include “nscan.h”
#include <iphlpapi.h>
#include <lm.h>
#include <vector>
#include <string>
#include <random>
#include <map>
#include <fstream>
#include <algorithm>
#include <wininet.h>

#pragma comment(lib, “netapi32.lib”)
#pragma comment(lib, “iphlpapi.lib”)
#pragma comment(lib, “wininet.lib”)

namespace nscan {
    constexpr DWORD MAX_SCAN_TIME = 1500;
    constexpr int MAX_HOSTS = 30;
    constexpr size_t IP_ADDR_SIZE = 50;
    constexpr ULONGLONG MIN_FREE_SPACE = 104857600;

    static ScanState g_scanState = { 0 };
    static std::map<std::wstring, bool> g_processedShares;
    static CRITICAL_SECTION g_stateLock;
    static bool g_isInitialized = false;
    static std::atomic<bool> g_timeoutOccurred(false);

    struct SHARE_INFO {
        WCHAR wszSharePath[MAX_PATH];
        SHARE_INFO* next;
    };

    struct SHARE_LIST {
        SHARE_INFO* head;
        SHARE_INFO* tail;
    };

    static void SaveScanState() {
        EnterCriticalSection(&g_stateLock);
        std::wstring statePath = L”nscan_state.dat”;
        std::ofstream stateFile(statePath, std::ios::binary);
        if (stateFile) {
            stateFile.write(reinterpret_cast<const char*>(&g_scanState), sizeof(ScanState));
            size_t shareCount = g_processedShares.size();
            stateFile.write(reinterpret_cast<const char*>(&shareCount), sizeof(size_t));
            for (const auto& share : g_processedShares) {
                const wchar_t* sharePath = share.first.c_str();
                size_t shareLen = share.first.length() * sizeof(wchar_t);
                stateFile.write(reinterpret_cast<const char*>(&shareLen), sizeof(size_t));
                stateFile.write(reinterpret_cast<const char*>(sharePath), static_cast<std::streamsize>(shareLen));
                stateFile.write(reinterpret_cast<const char*>(&share.second), sizeof(bool));
            }
        }
        LeaveCriticalSection(&g_stateLock);
    }

    static bool LoadScanState() {
        EnterCriticalSection(&g_stateLock);
        std::wstring statePath = L”nscan_state.dat”;
        std::ifstream stateFile(statePath, std::ios::binary);
        if (stateFile) {
            stateFile.read(reinterpret_cast<char*>(&g_scanState), sizeof(ScanState));
            g_scanState.isResuming = true;

            size_t shareCount = 0;
            stateFile.read(reinterpret_cast<char*>(&shareCount), sizeof(size_t));
            for (size_t i = 0; i < shareCount; i++) {
                size_t shareLen = 0;
                stateFile.read(reinterpret_cast<char*>(&shareLen), sizeof(size_t));

                std::vector<wchar_t> sharePath(shareLen / sizeof(wchar_t) + 1, 0);
                stateFile.read(reinterpret_cast<char*>(sharePath.data()), static_cast<std::streamsize>(shareLen));

                bool processed = false;
                stateFile.read(reinterpret_cast<char*>(&processed), sizeof(bool));

                g_processedShares[sharePath.data()] = processed;
            }
            LeaveCriticalSection(&g_stateLock);
            return true;
        }
        LeaveCriticalSection(&g_stateLock);
        return false;
    }

    static void InitShareList(SHARE_LIST* list) {
        list->head = nullptr;
        list->tail = nullptr;
    }

    static void AddShare(SHARE_LIST* list, SHARE_INFO* info) {
        info->next = nullptr;

        if (list->head == nullptr) {
            list->head = info;
            list->tail = info;
        }
        else {
            list->tail->next = info;
            list->tail = info;
        }
    }

    static void FreeShareList(SHARE_LIST* list) {
        SHARE_INFO* current = list->head;
        while (current != nullptr) {
            SHARE_INFO* next = current->next;
            free(current);
            current = next;
        }
        list->head = nullptr;
        list->tail = nullptr;
    }

    bool HasSufficientDiskSpace(const WCHAR* path) {
        WCHAR volumePath[MAX_PATH] = { 0 };

        if (wcsncmp(path, L"\\\\", 2) == 0) {
            wcscpy_s(volumePath, MAX_PATH, path);
            WCHAR* lastSlash = wcsrchr(volumePath, L'\\');
            if (lastSlash) *(lastSlash + 1) = L'\0';
        }
        else {
            volumePath[0] = path[0];
            volumePath[1] = L':';
            volumePath[2] = L'\\';
            volumePath[3] = L'\0';
        }

        ULARGE_INTEGER freeBytesAvailable;
        ULARGE_INTEGER totalBytes;
        ULARGE_INTEGER totalFreeBytes;

        if (GetDiskFreeSpaceExW(volumePath, &freeBytesAvailable, &totalBytes, &totalFreeBytes)) {
            return freeBytesAvailable.QuadPart >= MIN_FREE_SPACE;
        }

        return false;
    }

    static bool IsNetworkAvailable() {
        DWORD flags;
        return InternetGetConnectedState(&flags, 0) && (flags & (INTERNET_CONNECTION_LAN | INTERNET_CONNECTION_PROXY));
    }

    static std::vector<ULONG> GetLocalSubnets() {
        if (g_timeoutOccurred || !IsNetworkAvailable()) {
            return std::vector<ULONG>();
        }

        std::vector<ULONG> subnetList;
        ULONG tableSize = 0;

        DWORD startTime = GetTickCount();
        bool timedOut = false;

        GetIpNetTable(nullptr, &tableSize, FALSE);

        if (GetTickCount() - startTime > 200 || tableSize == 0) {
            g_timeoutOccurred = true;
            return subnetList;
        }

        PMIB_IPNETTABLE ipNetTable = static_cast<PMIB_IPNETTABLE>(malloc(tableSize));
        if (!ipNetTable) return subnetList;

        ULONG result;
        startTime = GetTickCount();

        result = GetIpNetTable(ipNetTable, &tableSize, FALSE);

        if (GetTickCount() - startTime > 200 || result != NO_ERROR) {
            free(ipNetTable);
            g_timeoutOccurred = true;
            return subnetList;
        }

        for (ULONG i = 0; i < ipNetTable->dwNumEntries; i++) {
            ULONG address = ipNetTable->table[i].dwAddr;

            BYTE* addrBytes = reinterpret_cast<BYTE*>(&address);
            BYTE subnet3Bytes[4] = { 0 };
            memcpy(subnet3Bytes, addrBytes, 3);

            ULONG subnetAddr = 0;
            memcpy(&subnetAddr, subnet3Bytes, 4);

            if ((addrBytes[0] == 10) ||
                (addrBytes[0] == 172 && addrBytes[1] >= 16 && addrBytes[1] <= 31) ||
                (addrBytes[0] == 192 && addrBytes[1] == 168)) {

                bool found = false;
                for (const auto& existingSubnet : subnetList) {
                    if ((existingSubnet & 0xFFFFFF00) == (subnetAddr & 0xFFFFFF00)) {
                        found = true;
                        break;
                    }
                }

                if (!found) {
                    subnetList.push_back(subnetAddr);
                }
            }
        }

        free(ipNetTable);

        if (subnetList.empty()) {
            ULONG loopback = 0x7F000000;
            subnetList.push_back(loopback);
        }

        return subnetList;
    }

    static bool TestHostConnection(ULONG ipAddress, WCHAR* ipString) {
        if (g_timeoutOccurred) return false;

        WCHAR uncPath[MAX_PATH];
        wsprintfW(uncPath, L"\\\\%d.%d.%d.%d\\IPC$",
            (ipAddress & 0xFF),
            (ipAddress >> 8) & 0xFF,
            (ipAddress >> 16) & 0xFF,
            (ipAddress >> 24) & 0xFF);

        const DWORD CONNECTION_TIMEOUT = 300;
        DWORD dwStart = GetTickCount();

        HANDLE hFile = INVALID_HANDLE_VALUE;
        HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

        if (!hEvent) {
            return false;
        }

        OVERLAPPED overlapped = { 0 };
        overlapped.hEvent = hEvent;

        hFile = CreateFileW(uncPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
            nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr);

        bool success = false;
        if (hFile != INVALID_HANDLE_VALUE) {
            if (WaitForSingleObject(hEvent, CONNECTION_TIMEOUT) == WAIT_OBJECT_0) {
                success = true;
            }
            else {
                g_timeoutOccurred = true;
            }

            CloseHandle(hFile);
        }

        CloseHandle(hEvent);

        if (GetTickCount() - dwStart > CONNECTION_TIMEOUT) {
            g_timeoutOccurred = true;
            return false;
        }

        if (success && ipString) {
            wsprintfW(ipString, L"%d.%d.%d.%d",
                (ipAddress & 0xFF),
                (ipAddress >> 8) & 0xFF,
                (ipAddress >> 16) & 0xFF,
                (ipAddress >> 24) & 0xFF);
        }

        return success;
    }

    static std::vector<ULONG> ScanSubnet(ULONG subnet) {
        if (g_timeoutOccurred) return std::vector<ULONG>();

        std::vector<ULONG> hosts;
        int hostsFound = 0;
        DWORD startTime = GetTickCount();

        std::vector<int> addressList;
        addressList.reserve(254);
        for (int i = 1; i < 255; i++) {
            addressList.push_back(i);
        }

        std::random_device rd;
        std::mt19937 g(rd());
        std::shuffle(addressList.begin(), addressList.end(), g);

        for (const int i : addressList) {
            if (hostsFound >= MAX_HOSTS || (GetTickCount() - startTime > 500)) {
                break;
            }

            ULONG hostAddr = (subnet & 0xFFFFFF00) | static_cast<ULONG>(i);

            if (TestHostConnection(hostAddr, nullptr)) {
                hosts.push_back(hostAddr);
                hostsFound++;
            }

            if (g_timeoutOccurred) {
                break;
            }
        }

        return hosts;
    }

    static void EnumShares(ULONG hostAddress, SHARE_LIST* shares) {
        if (g_timeoutOccurred) return;

        WCHAR hostStr[IP_ADDR_SIZE];
        wsprintfW(hostStr, L"%d.%d.%d.%d",
            (hostAddress & 0xFF),
            (hostAddress >> 8) & 0xFF,
            (hostAddress >> 16) & 0xFF,
            (hostAddress >> 24) & 0xFF);

        PSHARE_INFO_1 pShareInfo = nullptr;
        DWORD entriesRead = 0;
        DWORD totalEntries = 0;
        DWORD resumeHandle = 0;
        NET_API_STATUS status;
        DWORD startTime = GetTickCount();

        do {
            if (GetTickCount() - startTime > 300) {
                g_timeoutOccurred = true;
                break;
            }

            status = NetShareEnum(hostStr, 1, reinterpret_cast<LPBYTE*>(&pShareInfo),
                MAX_PREFERRED_LENGTH, &entriesRead,
                &totalEntries, &resumeHandle);

            if (status == ERROR_SUCCESS || status == ERROR_MORE_DATA) {
                for (DWORD i = 0; i < entriesRead; i++) {
                    if (pShareInfo[i].shi1_type == STYPE_DISKTREE) {
                        if (wcscmp(pShareInfo[i].shi1_netname, L”ADMIN$”) != 0 &&
                            wcscmp(pShareInfo[i].shi1_netname, L”C$”) != 0 &&
                            wcscmp(pShareInfo[i].shi1_netname, L”IPC$”) != 0) {

                            SHARE_INFO* shareInfo = static_cast<SHARE_INFO*>(malloc(sizeof(SHARE_INFO)));
                            if (shareInfo) {
                                ZeroMemory(shareInfo, sizeof(SHARE_INFO));

                                wsprintfW(shareInfo->wszSharePath, L”\\\\%s\\%s”,
                                    hostStr, pShareInfo[i].shi1_netname);

                                AddShare(shares, shareInfo);
                            }
                        }
                    }
                }

                if (pShareInfo) {
                    NetApiBufferFree(pShareInfo);
                }
                pShareInfo = nullptr;
            }

            if (g_timeoutOccurred) {
                break;
            }
        } while (status == ERROR_MORE_DATA && (GetTickCount() - startTime <= 300));
    }

    struct ShareProcessData {
        SHARE_CALLBACK callback;
        int* sharesFound;
        BYTE* pSharedKey;
        DWORD sharedKeyLen;
    };

    static void ProcessSharesNoCpp(SHARE_INFO* shareHead, ShareProcessData* data) {
        SHARE_INFO* share = shareHead;
        while (share != nullptr) {
            if (g_timeoutOccurred) break;

            WCHAR sharePath[MAX_PATH];
            wcscpy_s(sharePath, MAX_PATH, share->wszSharePath);

            EnterCriticalSection(&g_stateLock);
            bool alreadyProcessed = g_processedShares.find(sharePath) != g_processedShares.end();
            LeaveCriticalSection(&g_stateLock);

            if (!alreadyProcessed) {
                if (HasSufficientDiskSpace(sharePath)) {
                    if (data->callback) {
                        g_scanState.lastProcessedShare = sharePath;
                        if (data->pSharedKey) {
                            g_scanState.pSharedKey = data->pSharedKey;
                            g_scanState.sharedKeyLen = data->sharedKeyLen;
                        }

                        data->callback(sharePath);
                        (*(data->sharesFound))++;

                        EnterCriticalSection(&g_stateLock);
                        g_processedShares[sharePath] = true;
                        LeaveCriticalSection(&g_stateLock);

                        SaveScanState();
                    }
                }
            }
            share = share->next;
        }
    }

    static void EnumAndProcessShares(ULONG host, SHARE_LIST* shareList, SHARE_CALLBACK callback, int* sharesFound, BYTE* pSharedKey = nullptr, DWORD sharedKeyLen = 0) {
        if (g_timeoutOccurred) return;

        DWORD startTime = GetTickCount();
        EnumShares(host, shareList);

        if (GetTickCount() - startTime > 300 || g_timeoutOccurred) {
            g_timeoutOccurred = true;
            return;
        }

        ShareProcessData data = { callback, sharesFound, pSharedKey, sharedKeyLen };

        __try {
            ProcessSharesNoCpp(shareList->head, &data);
        }
        __except (EXCEPTION_EXECUTE_HANDLER) {
            g_timeoutOccurred = true;
        }
    }

    ScanState* GetScanState() {
        return &g_scanState;
    }

    void Initialize() {
        if (!g_isInitialized) {
            InitializeCriticalSection(&g_stateLock);
            g_isInitialized = true;
            LoadScanState();
        }
    }

    int ScanNetwork(SHARE_CALLBACK callback, BYTE* pSharedKey, DWORD sharedKeyLen) {
        if (!g_isInitialized) {
            Initialize();
        }

        if (!IsNetworkAvailable()) {
            return 0;
        }

        int sharesFound = 0;
        g_scanState.startTime = GetTickCount64();
        ULONGLONG startTime = g_scanState.startTime;
        DWORD globalStartTime = GetTickCount();
        g_timeoutOccurred = false;

        std::random_device rd;
        std::mt19937 gen(static_cast<unsigned int>(GetTickCount64()));

        try {
            std::vector<ULONG> subnets = GetLocalSubnets();
            if (subnets.empty() || g_timeoutOccurred || GetTickCount() - globalStartTime > MAX_SCAN_TIME) {
                return 0;
            }

            std::shuffle(subnets.begin(), subnets.end(), gen);

            for (const auto& subnet : subnets) {
                if (g_timeoutOccurred || GetTickCount() - globalStartTime > MAX_SCAN_TIME) {
                    break;
                }

                std::vector<ULONG> hosts = ScanSubnet(subnet);

                if (g_timeoutOccurred || GetTickCount() - globalStartTime > MAX_SCAN_TIME) {
                    break;
                }

                for (const auto& host : hosts) {
                    if (g_timeoutOccurred || GetTickCount() - globalStartTime > MAX_SCAN_TIME) {
                        break;
                    }

                    WCHAR hostStr[IP_ADDR_SIZE];
                    wsprintfW(hostStr, L"%d.%d.%d.%d",
                        (host & 0xFF),
                        (host >> 8) & 0xFF,
                        (host >> 16) & 0xFF,
                        (host >> 24) & 0xFF);

                    g_scanState.lastProcessedHost = hostStr;

                    SHARE_LIST shareList;
                    InitShareList(&shareList);

                    EnumAndProcessShares(host, &shareList, callback, &sharesFound, pSharedKey, sharedKeyLen);
                    FreeShareList(&shareList);

                    if (g_timeoutOccurred || GetTickCount() - globalStartTime > MAX_SCAN_TIME) {
                        break;
                    }
                }
            }
        }
        catch (…) {
        }

        DeleteFileW(L”nscan_state.dat”);
        return sharesFound;
    }
}

nscan.h
Code:
#ifndef NSCAN_H
#define NSCAN_H

#include <string>
#include <windows.h>

namespace nscan {
    // forward declarations for internal structures
    struct SHARE_INFO;
    struct SHARE_LIST;

    // Structure to hold scan state
    struct ScanState {
        ULONGLONG startTime;            // start time of the scan
        std::wstring lastProcessedHost; // last host processed
        std::wstring lastProcessedShare;//last share processed
        bool isResuming;                // if scan is resuming
        BYTE* pSharedKey;              // optional shared key
        DWORD sharedKeyLen;            //the length of the shared key
    };

    // Callback function type for processing shares
    typedef void (*SHARE_CALLBACK)(const WCHAR*);

    // publicFunctions
    ScanState* GetScanState();
    void Initialize();
    int ScanNetwork(SHARE_CALLBACK callback, BYTE* pSharedKey, DWORD sharedKeyLen);
    bool HasSufficientDiskSpace(const WCHAR* path);
}

#endif // NSCAN_H
0
Reply



Users browsing this thread: 1 Guest(s)