手戳shellcode编写 第一课(动态函数地址调用函数)

一、介绍

在程序免杀中,我们通常不会直接通过函数名称方式来调用函数,一般都是通过执行shellcode来执行我们需要的代码。如果直接将exe文件解析出来的shellcode会发现这些shellcode不能直接运行,因为函数地址调用的问题。因此我们需要动态获取函数地址来调用shellcode。为了了解shellcode的基础,我们先使用c++源码方式来学习,如果动态的获取函数地址,然后再使用汇编编写以下功能实现shellcode的完成编写。
https://arv000.blog.csdn.net/article/details/141233276

二、PE文件讲解

在编写思路之前我们需要大致了解一下PE文件格式,PE文件被映射到内存中。我们如何通过映射关系拿到对应的函数在内存中的地址。然后通过这个地址调用对应的函数。

2.1 PE文件类型

PE文件的全称是Portable Executable,意为可移植的可执行的文件,常见的EXE、DLL、OCX、SYS、COM都是PE文件,PE文件是微软Windows操作系统上的程序文件(可能是间接被执行,如DLL)

2.2 PE文件格式

下图是PE文件格式,因为只需要找到函数地址,我们只需要关注PE文件头中的(DOS头部、IMAGE_NT_HEADERS)就可以了。这个部分很重要,需要读者自己去查看相关资料。
每一个exe和dll都是按照下面的内容映射到内存中的,一个exe的运行,你可以理解为多个pe文件映射到内存中,有多个相同的一下结构。
在这里插入图片描述
在这里插入图片描述

2.3 相对虚拟地址直接调用函数

在dll中保存了函数的相对虚拟地址(RVA)的信息。
我们可以通过dumpbin方法拿到相对虚拟地址(RVA)的信息。
kernel32.dll文件存储在C:\Windows\System32中
打开Native Tools Command Prompt for VS 2022控制台程序
如下图:
在这里插入图片描述
执行命令

cd C:\Windows\System32
dumpbin /exports kernel32.dll | findstr LoadLibraryA

0001F520 为函数地址的偏移量。
在这里插入图片描述

获取函数LoadLibraryA的地址偏移量。
RVA = 基地址 + 地址偏移量

#include <iostream>
#include <Windows.h>
int main(){
	HANDLE baseAddress = LoadLibraryA("kernel32.dll");
	std::cout << "baseAddress:" << baseAddress << std::endl;
	std::cout << "LoadLibraryA:" << &LoadLibraryA << std::endl;
	long offset = (long)&LoadLibraryA - (long)baseAddress;
	std::cout << std::hex << "offset:" << offset << std::endl;
	return 0;
}

运行结果可以看到程序中的offset的值和dumpbin查询出来的值是一样的。
在这里插入图片描述

三、开发思路

思路如下:
我们应该先拿到LoadLibarayA的函数地址才能加载其他的DLL动态库(我们需要的动态库不一定在程序加载的时候就已经加载进来,因此需要使用LoadLibarayA加载我们需要的dll,例如例子中的MessageBoxA这个函数所在的动态库位user32.dll),那么LoadLibarayA又在那个动态库中呢?LoadLibarayA在KERNEL32.DLL动态库中,KERNEL32.DLL是程序运行一定加载的一个库。所以我们需要先找到KERNEL32.DLL基地址,然后再找LoadLibarayA的函数地址。
步骤如下:

  1. 先获取PEB(Process Environment Block,进程环境块)的信息。
  2. 通过PEB获取InLoadOrderModuleList(InLoadOrderModuleList列表中包含了所有加载的模块,包括exe文件以及各个需要加载的dll)
  3. 循环InLoadOrderModuleList内容,对比dllName名称找到KERNEL32.DLL基地址。
  4. 通过KERNEL32.DLL基地址拿到KERNEL32.DLL基地址导出表的地址。
  5. 通过名称对比拿到LoadLibarayA的函数地址。
  6. 通过LoadLibarayA地址调用LoadLibarayA函数,获取到user32.dll的模块基地址
  7. 同理通过user32.dll基地址以及名称获取MessageBoxA的函数地址。
  8. 通过函数地址调用MessageBoxA函数。

四、开发源码

4.1 环境说明

操作系统:window10
架构:x64
开发工具:visual studio 2022

4.2 获取本程序的所有加载模块

获取本程序的所有加载模块的名称,以及每个模块的基地址。
CMakeLists.txt文件内容如下:

cmake_minimum_required(VERSION 3.0)

project(printAllDLLModule)
file(GLOB SRC_LIST *.cpp phnt/*.h)
include_directories(phnt)
add_executable(${PROJECT_NAME} ${SRC_LIST})
#include "phnt/phnt_windows.h"
#include "phnt/phnt.h"
#include <iostream>
// 打印所有的dll模块内容。当然其中也包括exe本身。
void printAllDLLModule(){
    auto peb = (PEB*)NtCurrentTeb()->ProcessEnvironmentBlock;
    // 获取Ldr并遍历InLoadOrderModuleList来查找模块
    if (peb && peb->Ldr) {
        PLIST_ENTRY moduleList = &peb->Ldr->InLoadOrderModuleList;
        PLIST_ENTRY entry = moduleList->Flink;

        while (entry != moduleList) {
            // 获取当前模块的LDR_DATA_TABLE_ENTRY结构
            PLDR_DATA_TABLE_ENTRY currentModule = CONTAINING_RECORD(entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);

            // 打印模块名称和基址
            std::wcout << "Module Name: " << currentModule->BaseDllName.Buffer << std::endl;
            std::wcout << "Base Address: " << currentModule->DllBase << std::endl;

            // 继续下一个模块
            entry = entry->Flink;
        }
    } else {
        std::cerr << "Failed to retrieve PEB or PEB->Ldr is NULL." << std::endl;
    }
    return;
}

int main(){
    printAllDLLModule();
    return 0;
}

4.2 获取某一个模块的所有函数名称,以及函数地址。

获取某一个模块中的所有函数的函数名称以及虚拟函数地址。
CMakeLists.txt文件内容如下:

cmake_minimum_required(VERSION 3.0)

project(printAllFunctionName)
file(GLOB SRC_LIST *.cpp phnt/*.h)
include_directories(phnt)
add_executable(${PROJECT_NAME} ${SRC_LIST})
#include "phnt/phnt_windows.h"
#include "phnt/phnt.h"
#include <iostream>

void printAllFunctionName(HANDLE hModule){

    if (hModule == NULL) {
        std::cerr << "Failed to load DLL." << std::endl;
        return ;
    }

    // 获取PE头指针
    PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER)hModule;
    PIMAGE_NT_HEADERS pNTHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + pDOSHeader->e_lfanew);

    // 获取导出表的地址
    DWORD exportDirRVA = pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule + exportDirRVA);

    // 获取导出表的信息
    DWORD* pAddressOfFunctions = (DWORD*)((BYTE*)hModule + pExportDir->AddressOfFunctions);
    DWORD* pAddressOfNames = (DWORD*)((BYTE*)hModule + pExportDir->AddressOfNames);
    WORD* pAddressOfNameOrdinals = (WORD*)((BYTE*)hModule + pExportDir->AddressOfNameOrdinals);

    // 遍历所有导出函数
    for (DWORD i = 0; i < pExportDir->NumberOfNames; i++) {
        char* functionName = (char*)((BYTE*)hModule + pAddressOfNames[i]);
        DWORD functionRVA = pAddressOfFunctions[pAddressOfNameOrdinals[i]];
        void* functionAddress = (BYTE*)hModule + functionRVA;

            std::cout << "Function Name: " << functionName << std::endl;
            std::cout << "Function Address: " << functionAddress << std::endl;

    }
    return;
}
int main(){

    HANDLE hKernel32 = LoadLibraryA("kernel32.dll");
    printAllFunctionName(hKernel32);
    CloseHandle(hKernel32);

    return 0;
}

打印exe运行时模块中所有 的函数名称以及地址
https://download.csdn.net/download/arv002/89655996

  • 运行结果如下:
    在这里插入图片描述

4.4 C++完整源码

CMakeLists.txt文件内容如下:

cmake_minimum_required(VERSION 3.0)

project(demo)
file(GLOB SRC_LIST *.cpp phnt/*.h)
include_directories(phnt)
add_executable(${PROJECT_NAME} ${SRC_LIST})

main.cpp文件内容如下:

#include "phnt/phnt_windows.h"
#include "phnt/phnt.h"
#include <iostream>

HANDLE SreachDLL(WCHAR*  dllName){
    auto peb = (PEB*)NtCurrentTeb()->ProcessEnvironmentBlock;
    // 获取Ldr并遍历InLoadOrderModuleList来查找模块
    if (peb && peb->Ldr) {
        PLIST_ENTRY moduleList = &peb->Ldr->InLoadOrderModuleList;
        PLIST_ENTRY entry = moduleList->Flink;

        while (entry != moduleList) {
            // 获取当前模块的LDR_DATA_TABLE_ENTRY结构
            PLDR_DATA_TABLE_ENTRY currentModule = CONTAINING_RECORD(entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);

            // 打印模块名称和基址
            std::wcout << "Module Name: " << currentModule->BaseDllName.Buffer << std::endl;
            std::wcout << "Base Address: " << currentModule->DllBase << std::endl;

            if(!wcscmp(currentModule->BaseDllName.Buffer,dllName)){
                return currentModule->DllBase;
            }

            // 继续下一个模块
            entry = entry->Flink;
        }
    } else {
        std::cerr << "Failed to retrieve PEB or PEB->Ldr is NULL." << std::endl;
    }
    return nullptr;
}

void * GetProcNameE(HANDLE hModule,const char *funName){
    // 假设DLL基地址已经获取到,例如通过加载DLL的句柄

    if (hModule == NULL) {
        std::cerr << "Failed to load DLL." << std::endl;
        return nullptr;
    }

    // 获取PE头指针
    PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER)hModule;
    PIMAGE_NT_HEADERS pNTHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + pDOSHeader->e_lfanew);

    // 获取导出表的地址
    DWORD exportDirRVA = pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule + exportDirRVA);

    // 获取导出表的信息
    DWORD* pAddressOfFunctions = (DWORD*)((BYTE*)hModule + pExportDir->AddressOfFunctions);
    DWORD* pAddressOfNames = (DWORD*)((BYTE*)hModule + pExportDir->AddressOfNames);
    WORD* pAddressOfNameOrdinals = (WORD*)((BYTE*)hModule + pExportDir->AddressOfNameOrdinals);

    // 遍历所有导出函数
    for (DWORD i = 0; i < pExportDir->NumberOfNames; i++) {
        char* functionName = (char*)((BYTE*)hModule + pAddressOfNames[i]);
        DWORD functionRVA = pAddressOfFunctions[pAddressOfNameOrdinals[i]];
        void* functionAddress = (BYTE*)hModule + functionRVA;
        if(!strcmp(functionName, funName)){
            std::cout << "Function Name: " << functionName << std::endl;
            std::cout << "Function Address: " << functionAddress << std::endl;
            return functionAddress;
        }
    }
    return nullptr;
}
#define CALL_API(hModule,func) ((decltype(func)*)GetProcNameE(hModule,#func))
int main(){

    HANDLE hModule = SreachDLL(L"KERNEL32.DLL");
    typedef HMODULE(WINAPI *LoadLibraryFunc)(LPCSTR lpLibFileName);
    typedef int(WINAPI *MessageBoxAFunc)(HWND hWnd,LPCSTR lpText, LPCSTR lpCaption,UINT uType);
     // 函数执行方法一;
    // void * pLoadLibraryA = GetProcNameE(hModule,"LoadLibraryA");
    //  HANDLE hUser32 =  ((LoadLibraryFunc)pLoadLibraryA)("user32.dll");
    //  void * pMessageBoxA = GetProcNameE(hUser32,"MessageBoxA");
    // ((MessageBoxAFunc)pMessageBoxA)(NULL,"hello","hello",0);

    // 函数执行方法二:
    // HANDLE hUser32 = ((decltype(LoadLibraryA)*)GetProcNameE(hModule,"LoadLibraryA"))("user32.dll");
    // ((decltype(MessageBoxA)*)GetProcNameE(hUser32,"MessageBoxA"))(NULL,"hello","hello",0);

    // 函数执行方法三:
    HANDLE hUser32 = CALL_API(hModule,LoadLibraryA)("user32.dll");
    CALL_API(hUser32,MessageBoxA)(NULL,"hello","hello",0);
    return 0;
}

4.5 运行结果

在这里插入图片描述

五、源码地址

PPE解析+函数地址调用函数
https://download.csdn.net/download/arv002/89655989

打印exe运行时加载的所有dll模块地址以及模块名称
https://download.csdn.net/download/arv002/89655994

打印exe运行时模块中所有 的函数名称以及地址
https://download.csdn.net/download/arv002/89655996

文章地址:https://arv000.blog.csdn.net/article/details/141233276

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三雷科技

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值