ShellCode详解二

2024-05-12 08:36
文章标签 详解 shellcode

本文主要是介绍ShellCode详解二,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

终于到了期待已久的编写代码环节了,哈哈哈哈~

开始

首先,从上一章中我们了解到,shellcode代码是不可以包含任何的导入表的,所以我们写的所有shellcode代码都不可以调用系统库,这里说的不能调用是指不可以静态包含这些系统库。
那么,既然不能调用系统库,我们怎么实现我们的代码功能呢,总不能啥也不干,只用"printf"函数吧,哦,不好意思,“printf”也不可以调用,因为他也是系统库里的,哈哈哈,有木有一点崩溃呢。
相信聪明的同学已经猜到了,既然不能静态的调用,那么是不是可以动态调用呢,没错,实际上我们的shellcode代码是可以动态寻找系统库,然后调用的。

让我们重温一下调用其他库函数的流程:
1、首先,我们需要获取所需库的句柄,我们需要用到“LoadLibraryA”或"LoadLibraryW"这个API,其中前一个是Ansi编码的,后边是Unicode编码的,这个API在那个系统库呢,我们可以从MSDN中看到它属于"kernel32.dll"
2、当我们需要从库中获取某个函数的地址时我们需要使用"GetProcAddress"API,凑巧的是,这个API同样在"kernel32.dll"库中,是不是就减少了我们麻烦呢~

当我们知道上述的流程后,接下来就简单了,我们只需要在代码中获取"Kernel32.dll"加载的函数地址就可以了,这里提一句,因为这个库是系统库,所以每个进程一般都会包含且调用这个库,不一般的情况是什么呢,那就是像我们似的写了一个shellcode代码,咱不关心它,因为它不调用说明它啥事也没干。
好了,废话不多说,读这么多文字相信各位同学已经很难受了吧(PS:写这么多文字我也感觉难受)。

获取"kernel32.dll"在进程中的地址

要获取"kernel32.dll"在进程中的内存地址,有两种方法可以获取:
获取系统库在进程中的地址之前我们需要了解PEB结构是什么,大家可以自行去查阅获取,这里就不过多的做介绍了。
1、直接使用汇编获取模块的地址

__asm {mov eax,fs:[30h]mov eax,[eax+0ch]mov eax,[eax+14h]mov eax,[eax]mov eax,[eax]mov eax,[eax+10h]ret
}

上边这段代码仅适用于32位进程的获取,由于64位进程的特殊性,它并不适合在64位进程下获取相应的模块地址。PS:在xp系统下,模块地址的加载顺序为"exe本身"、“ntdll.dll”、“kernel32.dll”;win7以后的系统,模块地址加载顺序则为"exe本身"、“ntdll.dll”、“kernel32.dll”、“kernelbase.dll”。
2、解析模块加载的链表获取"kernel32.dll"的地址

#include <Windows.h>
#include <winternl.h>HMODULE GetKernel32BaseAddress()
{HMODULE hKernel32 = NULL;//保存模块名WCHAR wszModuleName[MAX_PATH];#ifdef _WIN64//获取gs偏移60hPPEB lpPeb = (PPEB)__readgsqword(0x60);
#else//获取fs偏移30hPPEB lpPeb = (PPEB)__readfsdword(0x30);
#endif//模块列表PLIST_ENTRY pListHead = &lpPeb->Ldr->InMemoryOrderModuleList;PLIST_ENTRY pListData = pListHead->Flink;while (pListData != pListHead){PLDR_DATA_TABLE_ENTRY pLDRData = CONTAINING_RECORD(pListData, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);//模块路径字符串数量DWORD dwLen = pLDRData->FullDllName.Length / 2;if (dwLen > 12){for (size_t i = 0; i < 12; i++){wszModuleName[11 - i] = pLDRData->FullDllName.Buffer[dwLen - 1 - i];}//kernel32.dllif ((wszModuleName[0] == 'k' || wszModuleName[0] == 'K') &&(wszModuleName[1] == 'e' || wszModuleName[1] == 'E') &&(wszModuleName[2] == 'r' || wszModuleName[2] == 'R') &&(wszModuleName[3] == 'n' || wszModuleName[3] == 'N') &&(wszModuleName[4] == 'e' || wszModuleName[4] == 'E') &&(wszModuleName[5] == 'l' || wszModuleName[5] == 'L') &&wszModuleName[6] == '3' &&wszModuleName[7] == '2' &&wszModuleName[8] == '.' &&(wszModuleName[9] == 'd' || wszModuleName[9] == 'D') &&(wszModuleName[10] == 'l' || wszModuleName[10] == 'L') &&(wszModuleName[11] == 'l' || wszModuleName[11] == 'L')){//kernel32.dll在进程中的基址hKernel32 = (HMODULE)pLDRData->DllBase;break;}}pListData = pListData->Flink;}return hKernel32;
}

上述代码主要是根据PEB结构,循环遍历链表,通过比对链表中的模块名获取其相应的地址。相对于第一种方法来说这种方式不仅适用于32位进程,同样适用于64位进程。原理则是读取"gs"和"fs"的偏移以获取对应的PEB结构,从而获取PEB中的模块地址。

获取"GetprocAddress"API的地址

从MSDN中我们知道此API是由"kernel32.dll"导出的,而在上述步骤中我们已经获取到了"kernel32.dll"在进程中的内存地址,剩下的就是解析其导出表,从而得到"GetprocAddress"函数的地址了。解析PE文件导入表需要对PE文件有基本的了解,PE文件的格式大家可以自行查阅,这里也不做过多的解析了,直接上代码:

#include <Windows.h>
#include <winternl.h>FARPROC GetPorcAddressBaseAddress()
{FARPROC pGetPorcAddress = NULL;HMODULE hKernel32 = GetKernel32BaseAddress();if (!hKernel32)return pGetPorcAddress;//获取Dos头PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hKernel32;//获取NT头PIMAGE_NT_HEADERS lpNtHeader = (PIMAGE_NT_HEADERS)((unsigned char*)hKernel32 + lpDosHeader->e_lfanew);if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size &&!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress){return pGetPorcAddress;}//导出表PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((unsigned char*)hKernel32 + lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);//函数名PDWORD lpdwFunName = (PDWORD)((unsigned char*)hKernel32 + lpExports->AddressOfNames);//函数序号PWORD lpdwOrd = (PWORD)((unsigned char*)hKernel32 + lpExports->AddressOfNameOrdinals);//函数地址PDWORD lpdwFunAddr = (PDWORD)((unsigned char*)hKernel32 + lpExports->AddressOfFunctions);for (DWORD dwLoop = 0; dwLoop < lpExports->NumberOfNames; dwLoop++){char* pFunName = (char*)(lpdwFunName[dwLoop] + (unsigned char*)hKernel32);//GetProcAddressif (pFunName[0] == 'G' && pFunName[1] == 'e' &&pFunName[2] == 't' && pFunName[3] == 'P' &&pFunName[4] == 'r' && pFunName[5] == 'o' &&pFunName[6] == 'c' && pFunName[7] == 'A' &&pFunName[8] == 'd' && pFunName[9] == 'd' &&pFunName[10] == 'r' && pFunName[11] == 'e' &&pFunName[12] == 's' && pFunName[13] == 's'){pGetPorcAddress = (FARPROC)(lpdwFunAddr[lpdwOrd[dwLoop]] + (unsigned char*)hKernel32);break;}}return pGetPorcAddress;
}

至此,我们已经做完初步解析工作了,剩下的就可以完成我们自己的功能函数了。

功能代码

我们来一个简单的功能,利用shellcode在进程中做一个弹窗,上代码:

#pragma comment(linker,"/entry:ShellCodeEntry")int ShellCodeEntry()
{typedef FARPROC(WINAPI* FN_GetProcAddress)(_In_ HMODULE hModule, _In_ LPCSTR lpProcName);typedef HMODULE(WINAPI* FN_LoadLibraryA)(_In_ LPCSTR lpLibFileName);FN_GetProcAddress fn_GetProcAddress;fn_GetProcAddress = (FN_GetProcAddress)GetPorcAddressBaseAddress();if (!fn_GetProcAddress)return 0;FN_LoadLibraryA fn_LoadlibraryA;//LoadLibraryAchar szLoadLibraryA[] = { 'L','o','a','d','L','i','b','r','a','r','y','A',0 };HMODULE hKernel32Address = GetKernel32BaseAddress();fn_LoadlibraryA = (FN_LoadLibraryA)fn_GetProcAddress(hKernel32Address, szLoadLibraryA);if (!fn_LoadlibraryA)return 0;typedef int (WINAPI* FM_MessageBoxA)(__in_opt HWND hWnd, __in_opt LPCSTR lpText, __in_opt LPCSTR lpCaption, __in UINT uType);char szUser32[] = { 'U','s','e','r','3','2','.','d','l','l',0 };char szMessageBoxA[] = { 'M','e','s','s','a','g','e','B','o','x','A',0 };FM_MessageBoxA fn_MessageBoxA = (FM_MessageBoxA)(fn_GetProcAddress(fn_LoadlibraryA(szUser32),szMessageBoxA));
=if (fn_MessageBoxA){char szText[] = { 'H','e','l','l','o',0 };fn_MessageBoxA(NULL,szText, 0, 0);}
}

这里有一点需要特别注意一下:
定义字符串的时候不要使用双引号,如:fn_MessageBoxA(NULL,“Hello”, 0, 0)
如果你定义了双引号的数据,这个双引号的数据就会以常量的方式保存到数据节中,我们使用shellcode的时候这个地址在其他进程中是找不到的,就会导致调用双引号字符串的地方出现未知的错误,无法正确的运行,这里需要特别注意一下。
当然,这里就没有方法解决了吗,是有的。github有一个开源的项目,其中就解决了这个问题,这里附上一份由imbyter改造过的代码:

obf.h

#pragma once// reference https://github.com/andrivet/ADVobfuscator#if defined(_MSC_VER)
#define ALWAYS_INLINE __forceinline
#else
#define ALWAYS_INLINE __attribute__((always_inline))
#endif#include <iomanip>
#include <iostream>// std::index_sequence will be available with C++14 (C++1y). For the moment, implement a (very) simplified and partial version. You can find more complete versions on the Internet
// MakeIndex<N>::type generates Indexes<0, 1, 2, 3, ..., N>namespace andrivet {namespace ADVobfuscator {template<int... I>struct Indexes { using type = Indexes<I..., sizeof...(I)>; };template<int N>struct Make_Indexes { using type = typename Make_Indexes<N - 1>::type::type; };template<>struct Make_Indexes<0> { using type = Indexes<>; };}
}// Very simple compile-time random numbers generator.// For a more complete and sophisticated example, see:
// http://www.researchgate.net/profile/Zalan_Szgyi/publication/259005783_Random_number_generator_for_C_template_metaprograms/file/e0b49529b48272c5a6.pdf#include <random>namespace andrivet {namespace ADVobfuscator {namespace{// I use current (compile time) as a seedconstexpr char time[] = __TIME__; // __TIME__ has the following format: hh:mm:ss in 24-hour time// Convert time string (hh:mm:ss) into a numberconstexpr int DigitToInt(char c) { return c - '0'; }const int seed = DigitToInt(time[7]) +DigitToInt(time[6]) * 10 +DigitToInt(time[4]) * 60 +DigitToInt(time[3]) * 600 +DigitToInt(time[1]) * 3600 +DigitToInt(time[0]) * 36000;}// 1988, Stephen Park and Keith Miller// "Random Number Generators: Good Ones Are Hard To Find", considered as "minimal standard"// Park-Miller 31 bit pseudo-random number generator, implemented with G. Carta's optimisation:// with 32-bit math and without divisiontemplate<int N>struct MetaRandomGenerator{private:static constexpr unsigned a = 16807;        // 7^5static constexpr unsigned m = 2147483647;   // 2^31 - 1static constexpr unsigned s = MetaRandomGenerator<N - 1>::value;static constexpr unsigned lo = a * (s & 0xFFFF);                // Multiply lower 16 bits by 16807static constexpr unsigned hi = a * (s >> 16);                   // Multiply higher 16 bits by 16807static constexpr unsigned lo2 = lo + ((hi & 0x7FFF) << 16);     // Combine lower 15 bits of hi with lo's upper bitsstatic constexpr unsigned hi2 = hi >> 15;                       // Discard lower 15 bits of histatic constexpr unsigned lo3 = lo2 + hi;public:static constexpr unsigned max = m;static constexpr unsigned value = lo3 > m ? lo3 - m : lo3;};template<>struct MetaRandomGenerator<0>{static constexpr unsigned value = seed;};// Note: A bias is introduced by the modulo operation.// However, I do belive it is neglictable in this case (M is far lower than 2^31 - 1)template<int N, int M>struct MetaRandom{static const int value = MetaRandomGenerator<N + 1>::value % M;};}
}namespace andrivet {namespace ADVobfuscator {struct HexChar{unsigned char c_;unsigned width_;HexChar(unsigned char c, unsigned width) : c_{ c }, width_{ width } {}};inline std::ostream& operator<<(std::ostream& o, const HexChar& c){return (o << std::setw(c.width_) << std::setfill('0') << std::hex << (int)c.c_ << std::dec);}inline HexChar hex(char c, int w = 2){return HexChar(c, w);}}
}namespace andrivet {namespace ADVobfuscator {// Represents an obfuscated string, parametrized with an alrorithm number N, a list of indexes Indexes and a key Keytemplate<int N, char Key, typename Indexes>struct MetaString;// Partial specialization with a list of indexes I, a key K and algorithm N = 0// Each character is encrypted (XOR) with the same keytemplate<char K, int... I>struct MetaString<0, K, Indexes<I...>>{// Constructor. Evaluated at compile time.constexpr ALWAYS_INLINE MetaString(const char* str): key_{ K }, buffer_{ encrypt(str[I], K)... } { }// Runtime decryption. Most of the time, inlinedinline const char* decrypt(){for (size_t i = 0; i < sizeof...(I); ++i)buffer_[i] = decrypt(buffer_[i]);buffer_[sizeof...(I)] = 0;//LOG("--- Implementation #" << 0 << " with key 0x" << hex(key_));return const_cast<const char*>(buffer_);}private:// Encrypt / decrypt a character of the original string with the keyconstexpr char key() const { return key_; }constexpr char ALWAYS_INLINE encrypt(char c, int k) const { return c ^ k; }constexpr char decrypt(char c) const { return encrypt(c, key()); }volatile int key_; // key. "volatile" is important to avoid uncontrolled over-optimization by the compilervolatile char buffer_[sizeof...(I)+1]; // Buffer to store the encrypted string + terminating null byte};// Partial specialization with a list of indexes I, a key K and algorithm N = 1// Each character is encrypted (XOR) with an incremented key.template<char K, int... I>struct MetaString<1, K, Indexes<I...>>{// Constructor. Evaluated at compile time.constexpr ALWAYS_INLINE MetaString(const char* str): key_(K), buffer_{ encrypt(str[I], I)... } { }// Runtime decryption. Most of the time, inlinedinline const char* decrypt(){for (size_t i = 0; i < sizeof...(I); ++i)buffer_[i] = decrypt(buffer_[i], i);buffer_[sizeof...(I)] = 0;//LOG("--- Implementation #" << 1 << " with key 0x" << hex(key_));return const_cast<const char*>(buffer_);}private:// Encrypt / decrypt a character of the original string with the keyconstexpr char key(size_t position) const { return static_cast<char>(key_ + position); }constexpr char ALWAYS_INLINE encrypt(char c, size_t position) const { return c ^ key(position); }constexpr char decrypt(char c, size_t position) const { return encrypt(c, position); }volatile int key_; // key. "volatile" is important to avoid uncontrolled over-optimization by the compilervolatile char buffer_[sizeof...(I)+1]; // Buffer to store the encrypted string + terminating null byte};// Partial specialization with a list of indexes I, a key K and algorithm N = 2// Shift the value of each character and does not store the key. It is only used at compile-time.template<char K, int... I>struct MetaString<2, K, Indexes<I...>>{// Constructor. Evaluated at compile time. Key is *not* storedconstexpr ALWAYS_INLINE MetaString(const char* str): buffer_{ encrypt(str[I])..., 0 } { }// Runtime decryption. Most of the time, inlinedinline const char* decrypt(){for (size_t i = 0; i < sizeof...(I); ++i)buffer_[i] = decrypt(buffer_[i]);//LOG("--- Implementation #" << 2 << " with key 0x" << hex(K));return const_cast<const char*>(buffer_);}private:// Encrypt / decrypt a character of the original string with the key// Be sure that the encryption key is never 0.constexpr char key(char key) const { return 1 + (key % 13); }constexpr char ALWAYS_INLINE encrypt(char c) const { return c + key(K); }constexpr char decrypt(char c) const { return c - key(K); }// Buffer to store the encrypted string + terminating null byte. Key is not storedvolatile char buffer_[sizeof...(I)+1];};// Helper to generate a keytemplate<int N>struct MetaRandomChar{// Use 0x7F as maximum value since most of the time, char is signed (we have however 1 bit less of randomness)static const char value = static_cast<char>(1 + MetaRandom<N, 0x7F - 1>::value);};}
}// Prefix notation
//#define DEF_OBFUSCATED(str) andrivet::ADVobfuscator::MetaString<andrivet::ADVobfuscator::MetaRandom<__COUNTER__, 3>::value, andrivet::ADVobfuscator::MetaRandomChar<__COUNTER__>::value, andrivet::ADVobfuscator::Make_Indexes<sizeof(str) - 1>::type>(str)
//#define OBFUSCATED(str) (DEF_OBFUSCATED(str).decrypt())#define DEF(str) andrivet::ADVobfuscator::MetaString<andrivet::ADVobfuscator::MetaRandom<__COUNTER__, 3>::value, andrivet::ADVobfuscator::MetaRandomChar<__COUNTER__>::value, andrivet::ADVobfuscator::Make_Indexes<sizeof(str) - 1>::type>(str)
#define O(str) (DEF(str).decrypt())

使用的时候我们仅需要包含这个头文件,然后使用的时候如下:fn_MessageBoxA(NULL, O(“Hello”), 0, 0)
这样我们就不需要定义字符串的时候单引号一个字节一个字节的定义字符串了。

OK,到这里,我们就写完了一个完整的shellcoed代码。
你以为这样就结束了吗?NO,这只是简单写完了一个符合shellcode代码规范的PE文件,它还不是一个shellcode,就算把生成的这个PE文件放到进程中它也是一个错误的,我们需要对它提取shellcode代码,然后放到进程中才可以无缝执行,具体的方式我么放到下一节中。

这篇关于ShellCode详解二的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/982124

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

K8S(Kubernetes)开源的容器编排平台安装步骤详解

K8S(Kubernetes)是一个开源的容器编排平台,用于自动化部署、扩展和管理容器化应用程序。以下是K8S容器编排平台的安装步骤、使用方式及特点的概述: 安装步骤: 安装Docker:K8S需要基于Docker来运行容器化应用程序。首先要在所有节点上安装Docker引擎。 安装Kubernetes Master:在集群中选择一台主机作为Master节点,安装K8S的控制平面组件,如AP

嵌入式Openharmony系统构建与启动详解

大家好,今天主要给大家分享一下,如何构建Openharmony子系统以及系统的启动过程分解。 第一:OpenHarmony系统构建      首先熟悉一下,构建系统是一种自动化处理工具的集合,通过将源代码文件进行一系列处理,最终生成和用户可以使用的目标文件。这里的目标文件包括静态链接库文件、动态链接库文件、可执行文件、脚本文件、配置文件等。      我们在编写hellowor

LabVIEW FIFO详解

在LabVIEW的FPGA开发中,FIFO(先入先出队列)是常用的数据传输机制。通过配置FIFO的属性,工程师可以在FPGA和主机之间,或不同FPGA VIs之间进行高效的数据传输。根据具体需求,FIFO有多种类型与实现方式,包括目标范围内FIFO(Target-Scoped)、DMA FIFO以及点对点流(Peer-to-Peer)。 FIFO类型 **目标范围FIFO(Target-Sc

019、JOptionPane类的常用静态方法详解

目录 JOptionPane类的常用静态方法详解 1. showInputDialog()方法 1.1基本用法 1.2带有默认值的输入框 1.3带有选项的输入对话框 1.4自定义图标的输入对话框 2. showConfirmDialog()方法 2.1基本用法 2.2自定义按钮和图标 2.3带有自定义组件的确认对话框 3. showMessageDialog()方法 3.1

脏页的标记方式详解

脏页的标记方式 一、引言 在数据库系统中,脏页是指那些被修改过但还未写入磁盘的数据页。为了有效地管理这些脏页并确保数据的一致性,数据库需要对脏页进行标记。了解脏页的标记方式对于理解数据库的内部工作机制和优化性能至关重要。 二、脏页产生的过程 当数据库中的数据被修改时,这些修改首先会在内存中的缓冲池(Buffer Pool)中进行。例如,执行一条 UPDATE 语句修改了某一行数据,对应的缓

OmniGlue论文详解(特征匹配)

OmniGlue论文详解(特征匹配) 摘要1. 引言2. 相关工作2.1. 广义局部特征匹配2.2. 稀疏可学习匹配2.3. 半稠密可学习匹配2.4. 与其他图像表示匹配 3. OmniGlue3.1. 模型概述3.2. OmniGlue 细节3.2.1. 特征提取3.2.2. 利用DINOv2构建图形。3.2.3. 信息传播与新的指导3.2.4. 匹配层和损失函数3.2.5. 与Super

web群集--nginx配置文件location匹配符的优先级顺序详解及验证

文章目录 前言优先级顺序优先级顺序(详解)1. 精确匹配(Exact Match)2. 正则表达式匹配(Regex Match)3. 前缀匹配(Prefix Match) 匹配规则的综合应用验证优先级 前言 location的作用 在 NGINX 中,location 指令用于定义如何处理特定的请求 URI。由于网站往往需要不同的处理方式来适应各种请求,NGINX 提供了多种匹