在后渗透利用开发中,反射型DLL和shellcode注入仍然是最大的威胁,因为其执行仅在内存中进行,且不会把任何内容拖放到磁盘上。但是,大多数offsec工具只有在初始访问或利用易受攻击的服务和进程时,才会注入shellcode。后渗透利用的攻击者通常会选择可以直接加载到内存的反射性DLL和C#可执行文件。但如果我们可以用更高级的语言(例如C语言)编写shellcode呢?本文将深入探讨链接器和编译器的滥用,用C语言编写shellcode,然后进行提取。
本文提到的所有代码都已上传到我的github(https://github.com/paranoidninja/PIC-Get-Privileges)上。
Windows编译的可执行文件有不同的头文件和段。它们通常以DOS标头、PE标头、可选标头开头,然后段主要时.text,.bss,.idata,.edata,.rdata等。用C语言编写shellcode,覆盖反射型DLL或C#可执行文件,向远程进程注入shellcode时,就算内存里有PE标头、DOS标头或其他明显的字符串,PE也不会被检测到。AMSI也不会检测shellcode,且shellcode的大小比DLL或C#可执行文件小得多。
用C语言编写shellcode时,我们必须注意编译后的可执行文件只有一个可执行文件段(.text段)。PE的全局变量储存在.bss段,.把DLL导入到.idata,导出到.edata。由于我们只要.text段,所以我们的代码中不能包含全局变量,导入的符号或静态字符串。我们的函数中不能包含基于函数字符串的char或wchar数组,因为char数组储存在.rdata段。而我们的目的是把所有内容都写入.text段,从.text段提取操作码,然后在内存中执行它们。也就是说,我们必须在运行时解决导入问题,且不能用静态库,把字节数组转换成字符串。默认情况下,链接器把PE的入口点链到mainCRTStartup,加载dll,解析命令行参数等。如果不想把msvcrt.dll链到可执行文件,那就要把这个入口点更改为其中一个函数。如果只编写x64 shellcode,那就必须确保shellcode以16字节的堆栈对齐方式对齐,。我们要编写独立于Microsoft的cl.exe编译器,且可以用MingW GCC交叉编译器进行编译的代码。
因此,要实现以上所有内容,必须满足以下条件:
-
16字节堆栈对齐
-
编译的可执行文件只有.text段
-
没有独立的char 数组或wchar数组字符串
-
在运行时解决所有导入
-
用自定义入口点替换mainCRTStartup函数的链接器脚本
为了确保shellcode始终对齐堆栈,需要编写一个小的程序集存根,它会让堆栈对齐,调用C语言函数作为入口点。把这个汇编代码转换为目标文件,然后将它链到C语言源代码。接着,编写一个函数,获取当前用户的特权信息,并把它输出到屏幕上,给这个C语言函数命名为getprivs。在汇编代码中,把这个函数添加为外部函数,因为我们不会把getprivs函数写入程序集。然后,把程序集文件转换为目标文件,并用C语言编写所有代码来完成所要执行的操作。
extern getprivs
global alignstack
segment .text
alignstack:
push rdi ; backup rdi since we will be using this as our main register
mov rdi, rsp ; save stack pointer to rdi
and rsp, byte -0x10 ; align stack with 16 bytes
sub rsp, byte +0x20 ; allocate some space for our C function
call getprivs ; call the C function
mov rsp, rdi ; restore stack pointer
pop rdi ; restore rdi
ret ; return where we left
把上面的asm文件命名为Adjuststack.asm,用mingw对其进行编译:
nasm -f win64 adjuststack.asm -o objects/adjuststack.o
既然要获取当前用户的特权信息,那么我们需要导出符号或WinAPI,即LoadLibraryA,CloseHandle,kernel32.dll的GetCurrentProcess,OpenProcessToken,GetTokenInformation,advapi32.dll的LookupPrivilegeNameW以及msvcrt.dll的calloc和wprintf。同时还要更改入口点,为了在创建目标文件时不让其被静态链到。在运行时,计算kernel32.dll的ror13哈希,找到LoadLibraryA的地址,然后用LoadLibraryA加载所需要的库,获取上述每个Windows导出符号的函数指针来解决这些导出问题。GetProcAddress是Windows API中最容易被AV和EDR钩住的函数,所以我们不用它。我们用C语言编写自己的GetProcAddress函数,该函数能解析在LoadLibraryA之后加载的DLL,提取符号的指针。把这些64位指针地址中的每个地址类型转换为我们的typedef。以下是所需符号的类型转换。
// kernel32.dll exports
typedef HMODULE(WINAPI* LOADLIBRARYA)(LPCSTR);
typedef BOOL(WINAPI* CLOSEHANDLE)(HANDLE);
typedef HANDLE(WINAPI* GETCURRENTPROCESS)();
// advapi32.dll exports
typedef BOOL(WINAPI* OPENPROCESSTOKEN)(HANDLE, DWORD, PHANDLE);
typedef BOOL(WINAPI* GETTOKENINFORMATION)(HANDLE, TOKEN_INFORMATION_CLASS, LPVOID, DWORD, PDWORD);
typedef BOOL(WINAPI* LOOKUPPRIVILEGENAMEW)(LPCWSTR, PLUID, LPWSTR, LPDWORD);
// msvcrt.dll exports
typedef int(WINAPI* WPRINTF)(const wchar_t* format, ...);
typedef void*(WINAPI* CALLOC)(size_t num, size_t size);
为了获取DLL的符号地址,需要用到下面的C语言函数,把该函数添加到addresshunter.h中:
//redefine UNICODE_STR struct
typedef struct _UNICODE_STR
{
USHORT Length;
USHORT MaximumLength;
PWSTR pBuffer;
} UNICODE_STR, *PUNICODE_STR;
//redefine PEB_LDR_DATA struct
typedef struct _PEB_LDR_DATA
{
DWORD dwLength;
DWORD dwInitialized;
LPVOID lpSsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
LPVOID lpEntryInProgress;
} PEB_LDR_DATA, * PPEB_LDR_DATA;
//redefine LDR_DATA_TABLE_ENTRY struct
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STR FullDllName;
UNICODE_STR BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
LIST_ENTRY HashTableEntry;
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
//redefine PEB_FREE_BLOCK struct
typedef struct _PEB_FREE_BLOCK
{
struct _PEB_FREE_BLOCK * pNext;
DWORD dwSize;
} PEB_FREE_BLOCK, * PPEB_FREE_BLOCK;
//redefine PEB struct
typedef struct __PEB
{
BYTE bInheritedAddressSpace;
BYTE bReadImageFileExecOptions;
BYTE bBeingDebugged;
BYTE bSpareBool;
LPVOID lpMutant;
LPVOID lpImageBaseAddress;
PPEB_LDR_DATA pLdr;
LPVOID lpProcessParameters;
LPVOID lpSubSystemData;
LPVOID lpProcessHeap;
PRTL_CRITICAL_SECTION pFastPebLock;
LPVOID lpFastPebLockRoutine;
LPVOID lpFastPebUnlockRoutine;
DWORD dwEnvironmentUpdateCount;
LPVOID lpKernelCallbackTable;
DWORD dwSystemReserved;
DWORD dwAtlThunkSListPtr32;
PPEB_FREE_BLOCK pFreeList;
DWORD dwTlsExpansionCounter;
LPVOID lpTlsBitmap;
DWORD dwTlsBitmapBits[2];
LPVOID lpReadOnlySharedMemoryBase;
LPVOID lpReadOnlySharedMemoryHeap;
LPVOID lpReadOnlyStaticServerData;
LPVOID lpAnsiCodePageData;
LPVOID lpOemCodePageData;
LPVOID lpUnicodeCaseTableData;
DWORD dwNumberOfProcessors;
DWORD dwNtGlobalFlag;
LARGE_INTEGER liCriticalSectionTimeout;
DWORD dwHeapSegmentReserve;
DWORD dwHeapSegmentCommit;
DWORD dwHeapDeCommitTotalFreeThreshold;
DWORD dwHeapDeCommitFreeBlockThreshold;
DWORD dwNumberOfHeaps;
DWORD dwMaximumNumberOfHeaps;
LPVOID lpProcessHeaps;
LPVOID lpGdiSharedHandleTable;
LPVOID lpProcessStarterHelper;
DWORD dwGdiDCAttributeList;
LPVOID lpLoaderLock;
DWORD dwOSMajorVersion;
DWORD dwOSMinorVersion;
WORD wOSBuildNumber;
WORD wOSCSDVersion;
DWORD dwOSPlatformId;
DWORD dwImageSubsystem;
DWORD dwImageSubsystemMajorVersion;
DWORD dwImageSubsystemMinorVersion;
DWORD dwImageProcessAffinityMask;
DWORD dwGdiHandleBuffer[34];
LPVOID lpPostProcessInitRoutine;
LPVOID lpTlsExpansionBitmap;
DWORD dwTlsExpansionBitmapBits[32];
DWORD dwSessionId;
ULARGE_INTEGER liAppCompatFlags;
ULARGE_INTEGER liAppCompatFlagsUser;
LPVOID lppShimData;
LPVOID lpAppCompatInfo;
UNICODE_STR usCSDVersion;
LPVOID lpActivationContextData;
LPVOID lpProcessAssemblyStorageMap;
LPVOID lpSystemDefaultActivationContextData;
LPVOID lpSystemAssemblyStorageMap;
DWORD dwMinimumStackCommit;
} _PEB, * _PPEB;
// main hashing function for ror13
__forceinline DWORD ror13( DWORD d )
{
return _rotr( d, 13 );
}
__forceinline DWORD hash( char * c )
{
register DWORD h = 0;
do
{
h = ror13( h );
h += *c;
} while( *++c );
return h;
}
// function to fetch the base address of kernel32.dll from the Process Environment Block
UINT64 GetKernel32() {
ULONG_PTR kernel32dll, val1, val2, val3;
USHORT usCounter;
// kernel32.dll is at 0x60 offset and __readgsqword is compiler intrinsic,
// so we don't need to extract it's symbol
kernel32dll = __readgsqword( 0x60 );
kernel32dll = (ULONG_PTR)((_PPEB)kernel32dll)->pLdr;
val1 = (ULONG_PTR)((PPEB_LDR_DATA)kernel32dll)->InMemoryOrderModuleList.Flink;
while( val1 ) {
val2 = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)val1)->BaseDllName.pBuffer;
usCounter = ((PLDR_DATA_TABLE_ENTRY)val1)->BaseDllName.Length;
val3 = 0;
//calculate the hash of kernel32.dll
do {
val3 = ror13( (DWORD)val3 );
if( *((BYTE *)val2) >= 'a' )
val3 += *((BYTE *)val2) - 0x20;
else
val3 += *((BYTE *)val2);
val2++;
} while( --usCounter );
// compare the hash kernel32.dll
if( (DWORD)val3 == KERNEL32DLL_HASH ) {
//return kernel32.dll if found
kernel32dll = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)val1)->DllBase;
return kernel32dll;
}
val1 = DEREF( val1 );
}
return 0;
}
// custom strcmp function since this function will be called by GetSymbolAddress
// which means we have to call strcmp before loading msvcrt.dll
// so we are writing our own my_strcmp so that we don't have to play with egg or chicken dilemma
int my_strcmp (const char *p1, const char *p2) {
const unsigned char *s1 = (const unsigned char *) p1;
const unsigned char *s2 = (const unsigned char *) p2;
unsigned char c1, c2;
do {
c1 = (unsigned char) *s1++;
c2 = (unsigned char) *s2++;
if (c1 == ' ') {
return c1 - c2;
}
}
while (c1 == c2);
return c1 - c2;
}
UINT64 GetSymbolAddress( HANDLE hModule, LPCSTR lpProcName ) {
UINT64 dllAddress = (UINT64)hModule,
symbolAddress = 0,
exportedAddressTable = 0,
namePointerTable = 0,
ordinalTable = 0;
if( hModule == NULL ) {
return 0;
}
PIMAGE_NT_HEADERS ntHeaders = NULL;
PIMAGE_DATA_DIRECTORY dataDirectory = NULL;
PIMAGE_EXPORT_DIRECTORY exportDirectory = NULL;
ntHeaders = (PIMAGE_NT_HEADERS)(dllAddress + ((PIMAGE_DOS_HEADER)dllAddress)->e_lfanew);
dataDirectory = (PIMAGE_DATA_DIRECTORY)&ntHeaders->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];
exportDirectory = (PIMAGE_EXPORT_DIRECTORY)( dllAddress + dataDirectory->VirtualAddress );
exportedAddressTable = ( dllAddress + exportDirectory->AddressOfFunctions );
namePointerTable = ( dllAddress + exportDirectory->AddressOfNames );
ordinalTable = ( dllAddress + exportDirectory->AddressOfNameOrdinals );
if (((UINT64)lpProcName & 0xFFFF0000 ) == 0x00000000) {
exportedAddressTable += ( ( IMAGE_ORDINAL( (UINT64)lpProcName ) - exportDirectory->Base ) * sizeof(DWORD) );
symbolAddress = (UINT64)( dllAddress + DEREF_32(exportedAddressTable) );
}
else {
DWORD dwCounter = exportDirectory->NumberOfNames;
while( dwCounter-- ) {
char * cpExportedFunctionName = (char *)(dllAddress + DEREF_32( namePointerTable ));
if( my_strcmp( cpExportedFunctionName, lpProcName ) == 0 ) {
exportedAddressTable += ( DEREF_16( ordinalTable ) * sizeof(DWORD) );
symbolAddress = (UINT64)(dllAddress + DEREF_32( exportedAddressTable ));
break;
}
namePointerTable += sizeof(DWORD);
ordinalTable += sizeof(WORD);
}
}
return symbolAddress;
}
既然要更改入口点,那么链接器也要知道这一点。把入口点重定向到程序集标签alignstack,对齐16字节的堆栈,然后调用getprivs()函数。我们不会编写任何int main()或void main(),所以要确保链接器对齐函数执行的顺序。下面是将执行该操作的链接器脚本。
ENTRY(alignstack)
SECTIONS
{
.text :
{
*(.text.alignstack)
*(.text.getprivs)
}
}
一切都设置好后,就可以编写入口点函数了。如果检查以上所有C语言代码,你会发现我们没有用任何全局变量或静态char数组字符串。但是大多数时候,我们必须用char数组字符串来格式化或打印shellcode的输出。在这种情况下,我们可以用char或wchar字节数组执行此操作,如下所示。在字节数组中编写char字符串,确保我们的字符串不在.bss段,编译器会强制把它包括在PE的.text段里:
CHAR *loadlibrarya_c = "LoadLibraryA"; // will become ->
CHAR loadlibrarya_c[] = {'L', 'o', 'a', 'd', 'L', 'i', 'b', 'r', 'a', 'r', 'y', 'A', 0};
获取当前用户特权的最终代码如下所示:
// kernel32.dll exports
typedef HMODULE(WINAPI* LOADLIBRARYA)(LPCSTR);
typedef BOOL(WINAPI* CLOSEHANDLE)(HANDLE);
typedef HANDLE(WINAPI* GETCURRENTPROCESS)();
// advapi32.dll exports
typedef BOOL(WINAPI* OPENPROCESSTOKEN)(HANDLE, DWORD, PHANDLE);
typedef BOOL(WINAPI* GETTOKENINFORMATION)(HANDLE, TOKEN_INFORMATION_CLASS, LPVOID, DWORD, PDWORD);
typedef BOOL(WINAPI* LOOKUPPRIVILEGENAMEW)(LPCWSTR, PLUID, LPWSTR, LPDWORD);
// msvcrt.dll exports
typedef int(WINAPI* WPRINTF)(const wchar_t* format, ...);
typedef void*(WINAPI* CALLOC)(size_t num, size_t size);
void getprivs() {
//dlls to dynamically load during runtime
UINT64 kernel32dll, msvcrtdll, advapi32dll;
//symbols to dynamically resolve from dll during runtime
UINT64 LoadLibraryAFunc, CloseHandleFunc,
OpenProcessTokenFunc, GetCurrentProcessFunc, GetTokenInformationFunc, LookupPrivilegeNameWFunc,
callocFunc, wprintfFunc;
// kernel32.dll exports
kernel32dll = GetKernel32();
CHAR loadlibrarya_c[] = {'L', 'o', 'a', 'd', 'L', 'i', 'b', 'r', 'a', 'r', 'y', 'A', 0};
LoadLibraryAFunc = GetSymbolAddress((HANDLE)kernel32dll, loadlibrarya_c);
CHAR getcurrentprocess_c[] = {'G', 'e', 't', 'C', 'u', 'r', 'r', 'e', 'n', 't', 'P', 'r', 'o', 'c', 'e', 's', 's', 0};
GetCurrentProcessFunc = GetSymbolAddress((HANDLE)kernel32dll, getcurrentprocess_c);
CHAR closehandle_c[] = {'C', 'l', 'o', 's', 'e', 'H', 'a', 'n', 'd', 'l', 'e', 0};
CloseHandleFunc = GetSymbolAddress((HANDLE)kernel32dll, closehandle_c);
// advapi32.dll exports
CHAR advapi32_c[] = {'a', 'd', 'v', 'a', 'p', 'i', '3', '2', '.', 'd', 'l', 'l', 0};
advapi32dll = (UINT64) ((LOADLIBRARYA)LoadLibraryAFunc)(advapi32_c);
CHAR openprocesstoken_c[] = {'O', 'p', 'e', 'n', 'P', 'r', 'o', 'c', 'e', 's', 's', 'T', 'o', 'k', 'e', 'n', 0};
OpenProcessTokenFunc = GetSymbolAddress((HANDLE)advapi32dll, openprocesstoken_c);
CHAR gettokeninformation_c[] = { 'G', 'e', 't', 'T', 'o', 'k', 'e', 'n', 'I', 'n', 'f', 'o', 'r', 'm', 'a', 't', 'i', 'o', 'n', 0 };
GetTokenInformationFunc = GetSymbolAddress((HANDLE)advapi32dll, gettokeninformation_c);
CHAR lookupprivilegenamew_c[] = {'L', 'o', 'o', 'k', 'u', 'p', 'P', 'r', 'i', 'v', 'i', 'l', 'e', 'g', 'e', 'N', 'a', 'm', 'e', 'W', 0};
LookupPrivilegeNameWFunc = GetSymbolAddress((HANDLE)advapi32dll, lookupprivilegenamew_c);
// msvcrt.dll exports
CHAR msvcrt_c[] = {'m', 's', 'v', 'c', 'r', 't', '.', 'd', 'l', 'l', 0};
msvcrtdll = (UINT64) ((LOADLIBRARYA)LoadLibraryAFunc)(msvcrt_c);
CHAR calloc_c[] = {'c', 'a', 'l', 'l', 'o', 'c', 0};
callocFunc = GetSymbolAddress((HANDLE)msvcrtdll, calloc_c);
CHAR wprintf_c[] = {'w', 'p', 'r', 'i', 'n', 't', 'f', 0};
wprintfFunc = GetSymbolAddress((HANDLE)msvcrtdll, wprintf_c);
DWORD cbSize = sizeof(TOKEN_ELEVATION), tpSize, length;
HANDLE hToken = NULL;
TOKEN_ELEVATION Elevation;
PTOKEN_PRIVILEGES tPrivs = NULL;
WCHAR name[256];
WCHAR priv_enabled[] = { L'[', L'+', L']', L' ', L'%', L'-', L'5', L'0', L'l', L's', L' ', L'E', L'n', L'a', L'b', L'l', L'e', L'd', L' ', L'(', L'D', L'e', L'f', L'a', L'u', L'l', L't', L')', L'n', 0 };
WCHAR priv_adjusted[] = { L'[', L'+', L']', L' ', L'%', L'-', L'5', L'0', L'l', L's', L' ', L'E', L'n', L'a', L'b', L'l', L'e', L'd', L' ', L'(', L'D', L'e', L'f', L'a', L'u', L'l', L't', L')', L'n', 0 };
WCHAR priv_disabled[] = { L'[', L'+', L']', L' ', L'%', L'-', L'5', L'0', L'l', L's', L' ', L'E', L'n', L'a', L'b', L'l', L'e', L'd', L' ', L'(', L'D', L'e', L'f', L'a', L'u', L'l', L't', L')', L'n', 0 };
WCHAR priv_elevated[] = {L'[', L'+', L']', L' ', L'E', L'l', L'e', L'v', L'a', L't', L'e', L'd', 0};
WCHAR priv_restricted[] = {L'[', L'+', L']', L' ', L'R', L'e', L's', L't', L'r', L'i', L'c', L't', L'e', L'd', 0};
if (((OPENPROCESSTOKEN)OpenProcessTokenFunc)(((GETCURRENTPROCESS)GetCurrentProcessFunc)(), TOKEN_QUERY, &hToken)) {
((GETTOKENINFORMATION)GetTokenInformationFunc)(hToken, TokenPrivileges, tPrivs, 0, &tpSize);
tPrivs = (PTOKEN_PRIVILEGES)((CALLOC)callocFunc)(tpSize+1, sizeof(TOKEN_PRIVILEGES));
if (tPrivs) {
if (((GETTOKENINFORMATION)GetTokenInformationFunc)(hToken, TokenPrivileges, tPrivs, tpSize, &tpSize)) {
for(int i=0; i<tPrivs->PrivilegeCount; i++){
length=256;
((LOOKUPPRIVILEGENAMEW)LookupPrivilegeNameWFunc)(NULL, &tPrivs->Privileges[i].Luid, name, &length);
if (tPrivs->Privileges[i].Attributes == 3) {
((WPRINTF)wprintfFunc)(priv_enabled, name);
} else if (tPrivs->Privileges[i].Attributes == 2) {
((WPRINTF)wprintfFunc)(priv_adjusted, name);
} else if (tPrivs->Privileges[i].Attributes == 0) {
((WPRINTF)wprintfFunc)(priv_disabled, name);
}
}
}
}
if (((GETTOKENINFORMATION)GetTokenInformationFunc)(hToken, TokenElevation, &Elevation, sizeof(Elevation), &cbSize)) {
if (Elevation.TokenIsElevated) {
((WPRINTF)wprintfFunc)(priv_elevated);
} else {
((WPRINTF)wprintfFunc)(priv_restricted);
}
}
((CLOSEHANDLE)CloseHandleFunc)(hToken);
}
}
为了编译以上所有内容,我们可以用makefile,如下所示:
make:
nasm -f win64 adjuststack.asm -o adjuststack.o
x86_64-w64-mingw32-gcc getprivs.c -Wall -m64 -ffunction-sections -fno-asynchronous-unwind-tables -nostdlib -fno-ident -O2 -c -o getprivs.o -Wl,-Tlinker.ld,--no-seh
x86_64-w64-mingw32-ld -s adjuststack.o getprivs.o -o getprivs.exe
上面的makefile执行以下操作:
* 为adjuststack创建一个目标文件
* 为getprivs创建一个64位目标文件。
-
为源文件中的每个函数生成单独段,并在链接期间删除未使用的函数。
-
禁用可执行文件的静态链接
-
优化PE的大小
-
禁用SEH
-
用链接器脚本提供的参数对齐函数可执行文件顺序
* 编译上面的目标文件,剥离所有调试符号和注释
运行脚本并使用objdump,可以从PE获得实际的shellcode。
(用objdump从可执行文件中获取shellcode。)
可以用objdump来检查文件是否有.text以外的任何标头。
最后,可以把shellcode复制到bin文件中,然后根据需要执行bin文件。
(用objdump查看从二进制文件获取的shellcode。)
在本文中,我会用ASM的inc-bin技术执行shellcode。
; compile with
; nasm -f win64 runshellcode.asm -o runshellcode.o
; x86_64-w64-mingw32-ld runshellcode.o -o runshellcode.exe
Global Start
Start:
incbin "getprivs.bin"
最终的可执行文件大小不可以超过5 KB,在我们的示例中,该文件的大小为4.9kb。
(用objdump查看从二进制文件获取的shellcode。)
最后,在Windows上执行此操作:
(用objdump查看从二进制文件获取的shellcode。)
用有特权的cmd提示符和无特权的cmd提示符,输出结果会有所不同。本文已经进入尾声了。我们将在Brute Ratel的下一个版本中添加功能更新,其中将包括在自身进程和远程进程的目标文件中执行Shellcode。
本文始发于微信公众号(木星安全实验室):在内存的目标文件上执行shellcode
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论