在笔者前几篇文章中,我们使用汇编语言并通过自定位的方法实现了一个简单的MessageBox弹窗功能,但由于汇编语言过于繁琐在编写效率上不仅要考验开发者的底层功底,还需要写出更多的指令集,这对于普通人来说是非常困难的,当然除了通过汇编来实现ShellCode的编写以外,使用C同样可以实现编写,在多数情况下读者可以直接使用C开发,只有某些环境下对ShellCode条件有极为苛刻的长度限制时才会考虑使用汇编。

相较于汇编语言,使用C编写Shellcode可以更加方便、高效,特别是对于需要大量计算的操作。在编写Shellcode时,读者需要注意以下几点:

  • 1.使用纯C语言进行编写:在编写Shellcode时,需要避免使用C++标准库或其他外部依赖库,因为这些库往往会增加代码的长度和复杂度。
  • 2.关闭编译器优化:在编写Shellcode时,需要关闭编译器的优化功能,因为优化可能会改变代码的执行顺序,导致Shellcode无法正常工作。
  • 3.避免使用全局变量和静态变量:在Shellcode中,全局变量和静态变量往往会导致代码长度过长,并且这些变量的地址也可能与Shellcode中其他代码的地址产生冲突。
  • 4.使用裸指针和裸内存管理:为了减小Shellcode的长度和复杂度,需要使用裸指针和裸内存管理,这可以减少代码中不必要的辅助函数调用。
  • 5.不能使用全局变量,或者用static修饰的变量,在Shellcode中要自定义入口函数,所有的字符串都要用字符串数组的方式代替。

首先读者应自行新建一个开发项目,并将编译模式调整为Release模式,这是因为Debug模式下的代码在转换成汇编后首先都是一个JMP指令,然后再跳到我们的功能代码处,但JMP指令是地址相关的 ,所以在转换成ShellCode时就会出错。此外在读者新建项目文件时请最好使用*.c结尾而不要使用*.cpp结尾。

当读者新建文件以后,接下来请修改配置属性,将运行库修改为多线程(MT)并关闭安全检查机制,如下图所示;

接着在连接器部分,新增一个EntryMain入口点,默认的Main入口点显然时不能使用的,如下图所示;

与前几章中的内容原理一致,首先我们需要得到kernel32.dll模块的基址,这段代码我们依然采用汇编实现,这里需要注意__declspec(naked)的含义,该声明是微软编译器提供的一个扩展,它用于指示编译器不要为函数自动生成函数头和尾,并将函数转化为裸函数。这种函数不会自动生成函数前缀和后缀的代码,也不会创建任何本地变量或保护寄存器。

在使用__declspec(naked)声明的函数中,开发者需要自己手动管理堆栈和调用函数的传递参数,然后在函数体中使用汇编指令实现所需的功能。使用__declspec(naked)声明的函数可以有效地减小生成的代码大小,因为不需要在函数前后添加额外的代码,而且可以精确控制函数内部的代码。

注意:使用__declspec(naked)声明的函数需要开发者对汇编语言有一定的了解,否则容易出现错误。在使用时,需要非常小心,确保在函数内部正确地管理堆栈和传递参数,以确保函数能够正常工作。

// ----------------------------------------------
// 32位获取模块基址
// ----------------------------------------------
__declspec(naked) DWORD getKernel32()
{
__asm
{
mov eax, fs: [30h]
mov eax, [eax + 0ch]
mov eax, [eax + 14h]
mov eax, [eax]
mov eax, [eax]
mov eax, [eax + 10h]
ret
}
} // ----------------------------------------------
// 64位获取模块基址
// ----------------------------------------------
/*
.code
getKernel32 proc
mov rax,gs:[60h]
mov rax,[rax+18h]
mov rax,[rax+30h]
mov rax,[rax]
mov rax,[rax]
mov rax,[rax+10h]
ret
getKernel32 endp
end
*/

当我们能够拿到kernel32.dll的模块基址时,则接下来就是通过该基址得到Kernel32的模块导出表,并获取该导出表内的GetProcessAddress函数的基址,至于为什么需要这么做,在读者前面的文章中有详细的分析,这里就不再重复叙述。

// ----------------------------------------------
// 32位取函数地址
// ----------------------------------------------
FARPROC getProcAddress(HMODULE hModuleBase)
{
PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase;
PIMAGE_NT_HEADERS32 lpNtHeader = (PIMAGE_NT_HEADERS)((DWORD)hModuleBase + lpDosHeader->e_lfanew);
if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size)
{
return NULL;
}
if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
{
return NULL;
} PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((DWORD)hModuleBase + (DWORD)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PDWORD lpdwFunName = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNames);
PWORD lpword = (PWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNameOrdinals);
PDWORD lpdwFunAddr = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfFunctions); DWORD dwLoop = 0;
FARPROC pRet = NULL;
for (; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++)
{
char* pFunName = (char*)(lpdwFunName[dwLoop] + (DWORD)hModuleBase); if (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')
{
pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (DWORD)hModuleBase);
break;
}
}
return pRet;
} // ----------------------------------------------
// 64位取函数地址
// ----------------------------------------------
/*
FARPROC getProcAddress(HMODULE hModuleBase)
{
PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase;
PIMAGE_NT_HEADERS64 lpNtHeader = (PIMAGE_NT_HEADERS64)((ULONG64)hModuleBase + lpDosHeader->e_lfanew);
if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size)
{
return NULL;
}
if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
{
return NULL;
}
PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((ULONG64)hModuleBase + (ULONG64)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PDWORD lpdwFunName = (PDWORD)((ULONG64)hModuleBase + (ULONG64)lpExports->AddressOfNames);
PWORD lpword = (PWORD)((ULONG64)hModuleBase + (ULONG64)lpExports->AddressOfNameOrdinals);
PDWORD lpdwFunAddr = (PDWORD)((ULONG64)hModuleBase + (ULONG64)lpExports->AddressOfFunctions); DWORD dwLoop = 0;
FARPROC pRet = NULL;
for (; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++)
{
char* pFunName = (char*)(lpdwFunName[dwLoop] + (ULONG64)hModuleBase); if (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')
{
pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (ULONG64)hModuleBase);
break;
}
}
return pRet;
}
*/

接着我们需要编写主代码逻辑,主代码逻辑中使用GetProcAddressLoadLibraryW来加载user32.dll并调用其中的MessageBoxW函数弹出一个消息框的示例。

下面是代码的详细实现流程:

  • 1.定义函数指针类型FN_GetProcAddress,用于存储GetProcAddress函数的地址,该函数用于在加载的DLL中查找导出函数的地址。
  • 2.通过getProcAddress函数获取kernel32.dll中的GetProcAddress函数地址,并将其转换为FN_GetProcAddress类型的函数指针fn_GetProcAddress。
  • 3.定义函数指针类型FN_LoadLibraryW,用于存储LoadLibraryW函数的地址,该函数用于加载指定的DLL文件。
  • 4.定义名为xyLoadLibraryW的字符数组,存储字符串"LoadLibraryW"。
  • 5.使用fn_GetProcAddress函数指针获取kernel32.dll中的LoadLibraryW函数的地址,并将其转换为FN_LoadLibraryW类型的函数指针fn_LoadLibraryW。
  • 6.定义函数指针类型FN_MessageBoxW,用于存储MessageBoxW函数的地址,该函数用于弹出消息框。
  • 7.定义名为xy_MessageBoxW的字符数组,存储字符串"MessageBoxW"。
  • 8.定义名为xy_user32的字符数组,存储字符串"user32.dll"。
  • 9.使用fn_LoadLibraryW函数指针加载user32.dll,并使用fn_GetProcAddress函数指针获取其中的MessageBoxW函数地址,并将其转换为FN_MessageBoxW类型的函数指针fn_MessageBoxW。
  • 10.定义名为MsgBox和Title的wchar_t数组,用于存储消息框的文本内容和标题。
  • 11.使用fn_MessageBoxW函数指针弹出一个消息框,显示MsgBox中的文本内容,并使用Title中的文本作为标题。
#include <Windows.h>

FARPROC getProcAddress(HMODULE hModuleBase);
DWORD getKernel32();
// extern "C" PVOID64 getKernel32(); // ----------------------------------------------
// 32位主函数
// ----------------------------------------------
int EntryMain()
{
// 定义指针,用于存储GetProcAddress入口地址
typedef FARPROC(WINAPI* FN_GetProcAddress)(
_In_ HMODULE hModule,
_In_ LPCSTR lpProcName
); FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32()); // 定义指针,用于存储LoadLibraryW入口地址
typedef HMODULE(WINAPI* FN_LoadLibraryW)(
_In_ LPCWSTR lpLibFileName
);
char xyLoadLibraryW[] = { 'L', 'o', 'a', 'd', 'L', 'i', 'b', 'r', 'a', 'r', 'y', 'W', 0 };
FN_LoadLibraryW fn_LoadLibraryW = (FN_LoadLibraryW)fn_GetProcAddress((HMODULE)getKernel32(), xyLoadLibraryW); // 定义指针,用于存储MessageBoxW入口地址
typedef int (WINAPI* FN_MessageBoxW)(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType);
wchar_t xy_user32[] = { 'u', 's', 'e', 'r', '3', '2', '.', 'd', 'l', 'l', 0 };
char xy_MessageBoxW[] = { 'M', 'e', 's', 's', 'a', 'g', 'e', 'B', 'o', 'x', 'W', 0 };
FN_MessageBoxW fn_MessageBoxW = (FN_MessageBoxW)fn_GetProcAddress(fn_LoadLibraryW(xy_user32), xy_MessageBoxW); // 此处用于设置MessageBoxW弹窗的文本内容
wchar_t MsgBox[] = { 'H', 'e', 'l', 'l', 'o', 'L', 'y', 'S', 'h','a','r','k', 0 };
wchar_t Title[] = { 'T', 'E', 'S', 'T', 0 };
fn_MessageBoxW(NULL, MsgBox, Title, NULL); return 0;
} // ----------------------------------------------
// 64位主函数
// ----------------------------------------------
/*
int EntryMain()
{
typedef FARPROC(WINAPI* FN_GetProcAddress)(
_In_ HMODULE hModule,
_In_ LPCSTR lpProcName
);
FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32()); typedef HMODULE(WINAPI* FN_LoadLibraryW)(
_In_ LPCWSTR lpLibFileName
);
char xyLoadLibraryW[] = { 'L', 'o', 'a', 'd', 'L', 'i', 'b', 'r', 'a', 'r', 'y', 'W', 0 };
FN_LoadLibraryW fn_LoadLibraryW = (FN_LoadLibraryW)fn_GetProcAddress((HMODULE)getKernel32(), xyLoadLibraryW); typedef int (WINAPI* FN_MessageBoxW)(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType);
wchar_t xy_user32[] = { 'u', 's', 'e', 'r', '3', '2', '.', 'd', 'l', 'l', 0 };
char xy_MessageBoxW[] = { 'M', 'e', 's', 's', 'a', 'g', 'e', 'B', 'o', 'x', 'W', 0 };
FN_MessageBoxW fn_MessageBoxW = (FN_MessageBoxW)fn_GetProcAddress(fn_LoadLibraryW(xy_user32), xy_MessageBoxW); wchar_t xy_Hello[] = { 'H', 'e', 'l', 'l', 'o', 'L', 'y', 'S', 'h', 'a', 'r', 'k', 0 };
wchar_t xy_tip[] = { 'T', 'E', 'S', 'T', 0 };
fn_MessageBoxW(NULL, xy_Hello, xy_tip, NULL); return 0;
}
*/

至此读者需要手动编译上述代码,当编译通过之后,请打开WinHex工具,并定位到ShellCode的开头位置,如下图所示则是我们需要提取的指令集;

选中这片区域,并右键点击编辑按钮,找到复制,C源码格式,此时读者即可得到一个完整的源代码格式;

至此读者只需要一个注入器用于测试代码的完善性,此处是简单实现的一个注入器,代码中shellcode是我们上图中提取出来的片段,读者需要修改targetPid为任意一个32位应用程序,并运行注入即可;

#include <windows.h>
#include <iostream> using namespace std; unsigned char shellcode[450] =
{
0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x5C, 0x53, 0x56, 0x57, 0xE8, 0xD2, 0x00, 0x00, 0x00, 0x8B, 0xC8,
0xE8, 0xEB, 0x00, 0x00, 0x00, 0x8B, 0xF0, 0xC7, 0x45, 0xD8, 0x4C, 0x6F, 0x61, 0x64, 0x8D, 0x45,
0xD8, 0xC7, 0x45, 0xDC, 0x4C, 0x69, 0x62, 0x72, 0x50, 0xC7, 0x45, 0xE0, 0x61, 0x72, 0x79, 0x57,
0xC6, 0x45, 0xE4, 0x00, 0xE8, 0xA7, 0x00, 0x00, 0x00, 0x50, 0xFF, 0xD6, 0x33, 0xC9, 0xC7, 0x45,
0xC0, 0x75, 0x00, 0x73, 0x00, 0x66, 0x89, 0x4D, 0xD4, 0x8D, 0x4D, 0xE8, 0x51, 0x8D, 0x4D, 0xC0,
0x5D, 0xC3
}; int main(int argc, char *argv[])
{
DWORD targetPid = 2816; HANDLE h_target = NULL;
LPVOID p_base = NULL;
HANDLE h_thread = NULL; h_target = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid);
if (h_target == NULL)
{
goto main_end;
} p_base = VirtualAllocEx(h_target, NULL, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (p_base == NULL)
{ goto main_end;
} if (!WriteProcessMemory(h_target, p_base, (LPVOID)shellcode, sizeof(shellcode), NULL)) {
goto main_end;
} h_thread = CreateRemoteThread(h_target, 0, 0, (LPTHREAD_START_ROUTINE)p_base, NULL, 0, NULL);
if (h_thread == NULL)
{
goto main_end;
} main_end:
if (h_target)
CloseHandle(h_target);
if (h_thread)
CloseHandle(h_thread);
getchar();
return 0;
}

如果一切顺利,则读者可看到这段ShellCode已经在特定进程内实现运行了,并输出了如下图所示的弹窗提示;

原文地址

https://www.lyshark.com/post/9cc4ded5.html

1.8 运用C编写ShellCode代码的更多相关文章

  1. 如何编写shellcode

    ShellCode的编写就是将函数或变量在内存中的间接地址改为函数或变量在内存中的直接地址,直接调用! 以MessageBox函数为例进行讲解如下 新建shellcode.cpp: 编写代码如下: 运 ...

  2. [转]通过Visual Studio为Linux编写C++代码

    Build 2016大会上Microsoft首次公布的Visual Studio 2015扩展提供了在VS2015中编写C++代码,随后通过Linux/UNIX计算机进行编译和执行的能力.这种想法非常 ...

  3. 基于CkEditor实现.net在线开发之路(2)编写C#代码,怎么调用它。

    上一章简约的介绍了CkEditor编辑器,可以编辑js逻辑代码,css,html,C#代码,这章我根据实际例子,讲解怎么编写C#代码和怎么调用它. 大家都还记得刚刚接触程序编时的hello Word吧 ...

  4. 解决VS2012编写JQuery代码不能智能提示的问题(其他js库的代码提示设置估计类似)

    VS默认设置下编写jQuery代码是这样的: 解决办法: 1.在项目的"管理NuGet程序包"中安装JQuery: 2.打开:工具 -> 选项 -> 文本编辑器 -&g ...

  5. JNI技术基础(2)——从零开始编写JNI代码

    书接上文: <JNI技术基础(1)——从零开始编写JNI代码> 2.编译源程序HelloWorld.java并生成HelloWorld.class 3.生成头文件HelloWorld.h ...

  6. 最新的JavaScript核心语言标准——ES6,彻底改变你编写JS代码的方式!【转载+整理】

    原文地址 本文内容 ECMAScript 发生了什么变化? 新标准 版本号6 兑现承诺 迭代器和for-of循环 生成器 Generators 模板字符串 不定参数和默认参数 解构 Destructu ...

  7. AppleWatch开发教程之Watch应用对象新增内容介绍以及编写运行代码

    AppleWatch开发教程之Watch应用对象新增内容介绍以及编写运行代码 添加Watch应用对象时新增内容介绍 Watch应用对象添加到创建的项目中后,会包含两个部分:Watch App 和 Wa ...

  8. mvn编写主代码与测试代码

    maven编写主代码与测试代码 3.2 编写主代码 项目主代码和测试代码不同,项目的主代码会被打包到最终的构件中(比如jar),而测试代码只在运行测试时用到,不会被打包.默认情况下,Maven假设项目 ...

  9. [JS进阶] 编写可维护性代码 (1)

    今天的web应用大至成千上万行的javascript代码,执行各种复杂的过程,这种演化让我们开发者必须得对可维护性有一定的认识!编写可维护性代码很重要,很多情况下我们是以他人的工作成果为基础,确保代码 ...

  10. Java认证:JavaRunnable线程编写接口代码

    Java认证:JavaRunnable线程编写接口代码.JavaRunnable线程如何才能更好的适应目前的编程环境呢?下面我们就看看如何才能更好的进行相关环境.希望下面的文章对大家有所帮助.Java ...

随机推荐

  1. classmethod和staticmethod装饰器

    """ 两个装饰器 @classmethod 把一个对象绑定的方法,修改成为一个类方法 1.在方法中仍然可以引用类中的静态变量 2.可以不用实例化对象,就直接使用类名在外 ...

  2. GraphQL渗透测试详解

    GraphQL介绍 GraphQL概述 GraphQL 是一种查询语言,用于 API 设计和数据交互.它是由 Facebook 发布的一款新型的数据查询和操作语言,自 2012 年起在内部使用,自 2 ...

  3. java跨越解决

    1.配置文件解决跨域 使用Filter方式进行设置 @Slf4j @Component public class CorsFilter implements Filter { @Override pu ...

  4. [Pytorch框架] 5.2 Pytorch处理结构化数据

    文章目录 5.2 Pytorch处理结构化数据 简介 数据预处理 定义数据集 定义模型 训练 import numpy as np import pandas as pd import torch f ...

  5. 2022-11-30:小红拿到了一个仅由r、e、d组成的字符串 她定义一个字符e为“好e“ : 当且仅当这个e字符和r、d相邻 例如“reeder“只有一个“好e“,前两个e都不是“好e“,只有第三个

    2022-11-30:小红拿到了一个仅由r.e.d组成的字符串 她定义一个字符e为"好e" : 当且仅当这个e字符和r.d相邻 例如"reeder"只有一个&q ...

  6. C# 中的“智能枚举”:如何在枚举中增加行为

    目录 枚举的基本用法回顾 枚举常见的设计模式运用 介绍 智能枚举 代码示例 业务应用 小结 枚举的基本用法回顾 以下是一个常见的 C# 枚举(enum)的示例: enum Weekday { Mond ...

  7. Net 如何获取私有属性

    .Net的私有属性.成员变量.方法,都可以通过反射获取调用,当然正常我们不会这么操作 此章只是做一个反射科普,像EFCore从数据库取值的底层框架就是通过反射直接操作私有的成员变量,而不是方法. 直接 ...

  8. 主流原型设计工具-Axure

    原型设计工具是一种用于设计和验证用户界面的软件工具,它可以帮助用户将界面设计想法转化为可交互的原型.以下是几种常见的原型设计工具: Axure:Axure是一款强大的原型设计工具,可以创建高保真的原型 ...

  9. C#/VB.NET:如何从 PowerPoint 演示文稿中提取文本

    在学习或者日常工作中,有时我们需要把幻灯片的东西整理成文字,而从 PowerPoint 演示文稿中一张一张的整理手动复制粘贴,整个过程会非常费精力且耗时.那么怎么样才能比较轻松且快速地提取PowerP ...

  10. 【Python&GIS】通过经纬度创建矢量点文件

         最近在做项目时,需要判断某个点是否在感兴趣区内.所以需要使用Python先根据经纬度的点创建矢量文件,再通过点文件和面文件的位置关系判断点是否在面内.         这里我们使用osgeo ...