1.8 运用C编写ShellCode代码

这篇具有很好参考价值的文章主要介绍了1.8 运用C编写ShellCode代码。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在笔者前几篇文章中,我们使用汇编语言并通过自定位的方法实现了一个简单的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)并关闭安全检查机制,如下图所示;

1.8 运用C编写ShellCode代码

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

1.8 运用C编写ShellCode代码

与前几章中的内容原理一致,首先我们需要得到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的开头位置,如下图所示则是我们需要提取的指令集;

1.8 运用C编写ShellCode代码

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

1.8 运用C编写ShellCode代码

至此读者只需要一个注入器用于测试代码的完善性,此处是简单实现的一个注入器,代码中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已经在特定进程内实现运行了,并输出了如下图所示的弹窗提示;

1.8 运用C编写ShellCode代码文章来源地址https://www.toymoban.com/news/detail-553501.html

到了这里,关于1.8 运用C编写ShellCode代码的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 1.6 编写双管道ShellCode后门

    本文将介绍如何将 CMD 绑定到双向管道上,这是一种常用的黑客反弹技巧,可以让用户在命令行界面下与其他程序进行交互,我们将从创建管道、启动进程、传输数据等方面对这个功能进行详细讲解。此外,本文还将通过使用汇编语言一步步来实现这个可被注入的 ShellCode 后门

    2024年02月12日
    浏览(224)
  • 1.5 编写自定位ShellCode弹窗

    在笔者上一篇文章中简单的介绍了如何运用汇编语言编写一段弹窗代码,虽然简易 ShellCode 可以被正常执行,但却存在很多问题,由于采用了硬编址的方式来调用相应API函数的,那么就会存在一个很大的缺陷,如果操作系统的版本不统或系统重启过,那么基址将会发生变化,

    2024年02月11日
    浏览(478)
  • c语言练手项目【编写天天酷跑游戏2.0】EASYX图形库的运用。代码开源,素材已打包

    素材加Q群:723550115 项目是基于Windows,easyX图形库进行开发的, 开发环境:Visual Studio 2022 项目技术最低要求: 常量,变量,数组,循环,函数。 使用initgraph()初始化界面; 加载背景资源 写好游戏的图像化界面的框架 由于easyX不支持透明图片的的贴图,所以我们要自己写一个

    2024年02月15日
    浏览(49)
  • [渗透测试]—7.1 漏洞利用开发和Shellcode编写

    在本章节中,我们将学习漏洞利用开发和Shellcode编写的基本概念和技巧。我们会尽量详细、通俗易懂地讲解,并提供尽可能多的实例。 漏洞利用开发是渗透测试中的高级技能。当你发现一个软件或系统存在漏洞时,你需要编写一段代码来利用这个漏洞,从而在目标系统上执行

    2024年02月11日
    浏览(47)
  • 网络攻防技术-Lab5-shellcode编写实验(SEED Labs – Shellcode Development Lab)

    网络攻防技术实验,实验环境、实验说明、实验代码见 Shellcode Development Lab 1) 编译mysh.s得到二进制文件 2) 执行 1)中的二进制文件 ,结果如下图, 我们 看到运行mysh之前的PID与运行mysh之后的PID是不同的,证明我们通过mysh启动了一个新的shell。 3) 获取机器码,以便进一步

    2023年04月13日
    浏览(42)
  • 「为什么代码要整洁?」——代码整洁度对于项目质量的影响,让我们通过这边文章来教你js和ts的代码整洁技巧,让你的项目更出众

    为什么代码要整洁? 代码质量与整洁度成正比。有的团队在赶工期的时候,不注重代码的整洁,代码写的越来越糟糕,项目越来越混乱,生产力也跟着下降,那就必须找更多人来提高生产力,开发成本越来越高。 整洁的代码是怎样的? 清晰表达意图、消除重复、简单抽象、

    2024年02月07日
    浏览(68)
  • 如何运用gpt改写出高质量的文章 (1)

    大家好,今天来聊聊如何运用gpt改写出高质量的文章 (1),希望能给大家提供一点参考。 以下是针对论文重复率高的情况,提供一些修改建议和技巧: 如何运用GPT改写出高质量的文章 一、引言 随着人工智能技术的飞速发展,自然语言处理领域取得了重大突破。GPT,作为其中

    2024年02月04日
    浏览(52)
  • 【让我们深度理解语音识别:Siri与Alexa如何运用深度学习和循环神经网络将声音转化为文字

    语音识别技术是近年来人工智能领域的重要研究对象,它的出现大大改善了人与机器的交互方式,增强了人们生活的便捷度。今天,我将带领大家深度了解语音识别技术的内部运作原理,以及语音助手如Siri和Alexa如何运用这些先进技术,实现从声音到文字的转化。在探讨技术

    2024年02月12日
    浏览(39)
  • 软件测试中的AI——运用AI编写测试用例

    2023.10.23更新: 经过一段时间的实践和沉淀之后,我将这段时间使用AI的经验,总结为教程,有兴趣可以前往新发表的文章:使用AI编写测试用例——详细教程 随着chatGPT的发布,点燃了人们对人工智能的热情,也同样引起了一场“人类将会被AI替代”的恐慌。 谁都知道现阶段

    2024年02月06日
    浏览(52)
  • 关于区块链的几篇论文的比较

    1.Blockchain Adoption for Combating Deceptive Counterfeits 主要专注于欺骗性假货,即客户无法真正区分真货和假货。 假设市场中只有一个制造商(M)销售真货给消费者,还有一个假货商(C)销售假货给消费者。 消费者对商品的估值是v,0-1均匀分布。 真货的质量是1,假货的质量是q1。

    2024年02月03日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包