1.5 编写自定位ShellCode弹窗

news2024/12/23 0:14:13

在笔者上一篇文章中简单的介绍了如何运用汇编语言编写一段弹窗代码,虽然简易ShellCode可以被正常执行,但却存在很多问题,由于采用了硬编址的方式来调用相应API函数的,那么就会存在一个很大的缺陷,如果操作系统的版本不统或系统重启过,那么基址将会发生变化,此时如果再次调用基址参数则会调用失败,本章将解决这个棘手的问题,通过ShellCode动态定位的方式解决这个缺陷,并以此设计出真正符合规范的ShellCode代码片段。

自定位代码是一种常见的Shellcode技术,它使Shellcode能够在任何系统上运行,而无需考虑系统内存布局和代码地址等问题。以下是Shellcode自定位代码的流程:

  • 1.查找Kernel32.dll基址并在其中寻找LoadLibraryA
  • 2.计算函数名hash摘要并通过hash摘要判断函数
  • 3.解析Kernel32.dll导出表
  • 4.最终动态调用系列函数

1.5.1 动态查找Kernel32基址

首先我们需要通过汇编的方式来实现动态定位Kernel32.dll中的基址,你或许会有个疑问? 为什么要查找Kernel32.dll的地址而不是User32.dll,这是因为我们最终的目的是调用MessageBoxA这个函数,而该函数位于 User32.dll这个动态链接库里,在某些程序中User32模块并不一定会被加载,而Kernel32则必然会被加载,为了能够调用MessageBoxA函数,我们就需要调用LoadLibraryA函数来加载User32.dll这个模块,而LoadLibraryA恰巧又位于kernel32.dll中,因此我们只需要找到LoadLibraryA函数,即可实现加载任意的动态链接库,并调用任意的函数的目的。

由于我们需要动态获取LoadLibraryA()以及ExitProcess()这两个函数的地址,而这两个函数又是存在于kernel32.dll中的,因此这里需要先找到kernel32.dll的基址,然后通过对其进行解析,从而查找两个函数的动态地址。动态的查找Kernel32.dll的地址可总结为如下:

  • 1.首先通过段选择子FS在内存中找到当前进程内的线程环境块结构体指针TEB。
  • 2.线程环境块偏移位置为fs:[0x30]的位置处存放着指向进程环境块PEB结构的指针。
  • 3.进程环境块PEB偏移为0x0c的地址处存放着指向PEB_LDR_DATA的结构体指针。
  • 4.而在PEB_LDR_DATA偏移0x1c的地址处存放着指向Ldr.InMemoryOrderModuleList模块初始化链表的头指针。
  • 5.在初始化链表中存放的就是所有进程的模块信息,通过将偏移值加0x08读者即可获取到kernel32.dll的基地址。

既然有了固定的查询定位公式,接下我们就使用WinDBG调试器来手工完成对Kernel32.dll地址的定位:

小提示:Windbg是Windows Debugger的缩写,是一种微软提供的免费调试器工具,用于分析和调试Windows操作系统和应用程序。Windbg可以在不重启系统的情况下,通过连接到正在运行的进程或者操作系统内核,获取并分析程序的运行信息、内存状态、寄存器状态、线程状态、调用堆栈等数据,并可以使用符号文件来解析程序中的符号名,从而帮助开发者定位问题和进行深入调试。

读者可通过附件获取到WinDBG程序,当用户打开WinDBG时读者可通过Ctrl+E快捷键任意打开一个可执行程序,接着我们开始寻找吧;

1.通过段选择子FS在内存中找到当前的线程环境块TEB。这里可以利用本地调试,并输入!teb指令,读者可看到如下输出:

小提示:TEB(Thread Environment Block)是Windows操作系统中的一个重要数据结构,每个进程都有一个对应的TEB。它主要用于存储线程的环境信息和状态,包括线程局部存储(TLS)指针、异常处理链、堆栈信息、Fiber信息等。TEB由Windows内核自动创建和管理,可以通过系统调用和调试器工具来访问和修改其内容。

如上线程环境块偏移位置为0x30的地方存放着指向进程环境块PEB的指针。结合上图可见,当前PEB的地址为002bb000

小提示:PEB是Windows操作系统的进程环境块(Process Environment Block)的缩写。PEB是一个数据结构,其中包含了关于进程的许多信息,例如进程的模块、堆、线程等等。PEB由操作系统内核在创建进程时分配和初始化,并且只有在进程运行期间才可用。

2.在进程环境块中偏移位置为0x0c的地方存放着指向PEB_LDR_DATA结构体的指针,其中存放着已经被进程装载的动态链接库的信息,如下图所示;

3.接着PEB_LDR_DATA结构体偏移位置为0x1c的地方存放着指向模块初始化链表的头指针InInitializationOrderModuleList,如下图所示;

4.模块初始化链表InInitializationOrderModuleList中按顺序存放着PE装入运行时初始化模块的信息,第一个链表节点是ntdll.dll,第二个链表结点就是kernel32.dll。我们可以先看看
InInitializationOrderModuleList中的内容:

上图中的0x005a3ad8保存的是第一个链节点的指针,解析一下这个结点,可发现如下地址:

上图中的0x77200000ntdll.dll的模块基地址,而0x005a4390则是指向下一个模块的指针,我们继续跟随0x005a4390地址,则此处看到的标黄处是下一个模块kernel32.dll的基地址。

最后我们通过输入!peb命令,输出当前所有载入模块并验证一下:

既然有了如上所述的方法,那么读者可以很容易的实现这段功能,为了便于读者理解,笔者先提供一段使用C语言书写的实现方式,如下代码所示;

#include <windows.h>
#include <stdio.h>

int main(int argc, char * argv[])
{
    DWORD *PEB = NULL;
    DWORD *Ldr = NULL;
    DWORD *Init = NULL;
    DWORD *Kernel32 = NULL;

    __asm
    {
        mov eax, fs:[0x30]
        mov PEB,eax
    }
    printf("得到PEB指针 = %x \n", PEB);

    Ldr = *(DWORD **)((unsigned char *)PEB + 0x0c);
    printf("得到LDR结构指针 = %x \n", Ldr);

    Init = *(DWORD **)((unsigned char *)Ldr + 0x1c);
    printf("得到InInitializationOrderModuleList结构指针 = %x \n", Init);

    Kernel32 = *(DWORD **)((unsigned char *)Init + 0x08);
    printf("得到Kernel32的基地址 = %x \n", Kernel32);

    system("pause");
    return 0;
}

运行输出效果如下图所示,读者可自行检查读取结果的准确性;

将此段代码翻译为汇编模式也很容易,如下是通过汇编实现的流程;

    .386p
    .model flat,stdcall
    option casemap:none

include windows.inc
include kernel32.inc
includelib kerbcli.lib
assume fs:nothing

.code
    main PROC
        xor eax,eax
        xor edx,edx
        mov eax,fs:[30h]           ; 得到PEB结构地址
        mov eax,[eax + 0ch]        ; 得到PEB_LDR_DATA结构地址
        mov esi,[eax + 1ch]        ; 得到 InInitializationOrderModuleList
        lodsd                      ; 得到KERNEL32.DLL所在LDR_MODULE结构的
        mov eax,[eax]              ; Windows 7 以上要将这里打开
        mov edx,[eax + 8h]         ; 得到BaseAddress,既Kernel32.dll基址
        ret
    main ENDP
END main

1.5.2 动态查找并枚举进程模块

在读者阅读过第一节中的内容时,相信您已经可以熟练的掌握WinDBG调试器的基本使用了,本节我们将扩展一个知识点,以让读者能更好的理解WinDBG调试命令,本次我们实现枚举进程模块的功能,本案例将不在解释基本功能。

通过PEB/TEB找到自身进程的所有载入模块数据,首先获取TEB线程环境块。在编程的时候,TEB始终保存在寄存器FS中。

得到LDR结构:Ldr = *( ( DWORD ** )( ( unsigned char * )PEB + 0x0c ) );

然后再找到PEB结构偏移为0x30从该命令的输出可以看出,PEB结构体的地址位于TEB结构体偏移0x30的位置处。

找到了PEB也就可以找到_PEB_LDR_DATA结构 其位于PEB偏移0c的位置上。

Ldr = *( ( DWORD ** )( ( unsigned char * )PEB + 0x0c ) );

从输出结果可以看出,LDRPEB结构体偏移的0x0C处,该地址保存的地址是0x77325d80通过该地址来解析LDR结构体。

Flink = *( ( DWORD ** )( ( unsigned char * )Ldr + 0x14 ) );

位于LDR偏移14的位置就是InLoadOrderModuleList其所指向的就是模块名称表。

现在来手动遍历[ 0x5a3bd0 - 0x5aa5b8 ]第一条链表,输入命令dd 0x5a3bd0

链表偏移0x18的位置是模块的映射地址 ImageBase,链表偏移 0x28 的位置是模块的路径及名称的地址,链表偏移 0x30 的位置是模块名称的地址。

如上图中的输出结果,地址005a2480保存有当前模块详细路径信息,而005a24ae则保存有当前模块名,我们可以通过du命令来进行验证;

当读者需要读入下一个模块链表时,则需要访问0x005a3ac8这个内存地址,其中保存着下一个链表结构,依次遍历。

当然这个链表结构其实访问InMemoryOrderModuleList同样可以得到,这两个都指向同一片区域。

上方介绍的结构,是微软保留结构,只能从网上找到一个结构定义,根据该结构的定义做进一步解析即可。

typedef struct _LDR_DATA_TABLE_ENTRY {
    PVOID Reserved1[2];
    LIST_ENTRY InMemoryOrderLinks;
    PVOID Reserved2[2];
    PVOID DllBase;
    PVOID EntryPoint;
    PVOID Reserved3;
    UNICODE_STRING FullDllName;
    BYTE Reserved4[8];
    PVOID Reserved5[3];
    union {
    ULONG CheckSum;
    PVOID Reserved6;
    };
    ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

根据如上分析细节,那么描述枚举模块列表的核心代码就可以写成如下案例;

#include <Windows.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    DWORD *PEB = NULL, *Ldr = NULL, *Flink = NULL, *p = NULL;
    DWORD *BaseAddress = NULL, *FullDllName = NULL,*Ba = NULL;

    __asm
    {
        mov eax, fs:[0x30]
        mov PEB, eax
    }

    Ldr = *((DWORD **)((unsigned char *)PEB + 0x0c));
    Flink = *((DWORD **)((unsigned char *)Ldr + 0x14));
    p = Flink;

    p = *((DWORD **)p);
    while (Flink != p)
    {
        BaseAddress = *((DWORD **)((unsigned char *)p + 0x10));
        FullDllName = *((DWORD **)((unsigned char *)p + 0x20));

    if (BaseAddress == 0)
        break;

    printf("镜像基址 = %08x \n --> 模块路径 = %S \n", BaseAddress, (unsigned char *)FullDllName);

        p = *((DWORD **)p);
    }
    system("pause");
    return 0;
}

读者编译并运行该程序,则默认会枚举出当前模块所导入的所有模块信息,其输出效果如下图所示;

1.5.3 计算函数Hash摘要值

案例介绍了如何使用Win32汇编语言和C语言计算字符串的hash摘要值。字符串的hash摘要值是通过一定的算法将字符串压缩为一个固定长度的十六进制数,用于在程序中进行快速的字符串比较。具体而言,该案例使用了循环移位hash计算法,并最终得到了字符串的 hash 值,并以十六进制数的形式输出。

读者一定有疑问为啥需要HASH压缩处理? 原因是,如果直接将函数名压栈的话,我们就需要提供更多的空间来存储ShellCode代码,为了能够让我们编写的ShellCode代码更加的短小精悍,所以我们将要对字符串进行hash处理,将字符串压缩为一个十六进制数,这样只需要比较二者hash值就能够判断目标函数,尽管这样会引入额外的hash算法,但是却可以节省出存储函数名字的空间。

为了能让读者理解计算原理,此处我们先使用C语言做摘要计算描述,如下代码中的GetHash函数,该函数接受一个指向字符数组的指针,即一个字符串,然后对字符串进行哈希计算,并返回计算结果。

哈希计算的过程是通过循环遍历字符串中的每个字符,对其进行位运算和加法运算,最终得到一个32位的哈希值。对于字符串中的每个字符,程序首先将哈希值左移25位,然后将结果右移7位,相当于是对哈希值进行了循环右移25位。然后程序将该字符的ASCII值加到哈希值上。循环遍历完字符串中的所有字符后,哈希值即为最终的计算结果。

#include <stdio.h>
#include <windows.h>

DWORD GetHash(char *fun_name)
{
    DWORD digest = 0;
    while (*fun_name)
    {
        digest = ((digest << 25) | (digest >> 7));
        digest += *fun_name;
        fun_name++;
    }
    return digest;
}

int main(int argc, char *argv[])
{
    DWORD MessageBoxHash;
    DWORD ExitProcessHash;
    DWORD LoadLibraryAHash;

    MessageBoxHash = GetHash("MessageBoxA");
    printf("MessageBoxHash = 0x%.8x\n", MessageBoxHash);

    ExitProcessHash = GetHash("ExitProcess");
    printf("ExitProcessHash = 0x%.8x\n", ExitProcessHash);

    LoadLibraryAHash = GetHash("LoadLibraryA");
    printf("LoadLibraryAHash = 0x%.8x\n", LoadLibraryAHash);

    system("pause");
    return 0;
}

运行上方C语言实现代码,则读者可以此获取到三个核心函数的Hash值,其输出效果如下图所示;

在理解了C语言版本的计算流程后,那么汇编语言版本的也应该很容易理解,如下是使用Win32汇编语言的实现过程,并在MASM上正常编译,汇编版字符串转换Hash值。

    .386p
    .model flat,stdcall
    option casemap:none
    
include windows.inc
include kernel32.inc
include msvcrt.inc
includelib kernel32.lib
includelib msvcrt.lib

.data
    data db "MessageBoxA",0h
    Fomat db "0x%x",0
.code
    main PROC
        xor eax,eax               ; 清空eax寄存器
        xor edx,edx               ; 清空edx寄存器
        lea esi,data              ; 取出字符串地址
    loops:
        movsx eax,byte ptr[esi]   ; 每次取出一个字符放入eax中
        cmp al,ah                 ; 验证eax是否为0x0即结束符
        jz nops                   ;0则说明计算完毕跳转到nops
        ror edx,7                 ; 不为零,则进行循环右移7位
        add edx,eax               ; 将循环右移的值不断累加
        inc esi                   ; esi自增,用于读取下一个字符
        jmp loops                 ; 循环执行
    nops:
        mov eax,edx               ; 结果存在eax里面
        invoke crt_printf,addr Fomat,eax
        ret
    main ENDP
END main

1.5.4 枚举Kernel32导出表

在文章开头部分我们通过WinDBG调试器已经找到了Kernel32.dll这个动态链接库的基地址,而Dll文件本质上也是PE文件,在Dll文件中同样存在导出表,其内部记录着该Dll的导出函数。接着我们需要对Dll文件的导出表进行遍历,不断地搜索,从而找到我们所需要的API函数,同样的可以通过如下定式获取到指定的导出表。

  • 1.从kernel32.dll加载基址算起,偏移0x3c的地方就是其PE文件头。
  • 2.PE文件头偏移0x78的地方存放着指向函数导出表的指针。
  • 3.导出表偏移0x1c处的指针指向存储导出函数偏移地址(RVA)的列表。
  • 4.导出表偏移0x20处的指针指向存储导出函数函数名的列表。

首先我们通过WinDBG来实现读取导入表及导出表试试,我们以读取ole32.dll为例,首先读者需要通过lmvm ole32.dll查询到该模块的入口地址,如图所示该模块的入口地址为0x75830000

解析DOS头,DOS头通过_IMAGE_DOS_HEADER结构被定义,在解析时读者应传入模块入口0x75830000地址,其次DOS头中e_lfanew字段指向了PE头,该字段需要注意;

  • 执行读入DOS头:dt ole32!_IMAGE_DOS_HEADER 75830000

解析PE头,PE头通过DOS头部的e_lfanew中存储的之加上模块基地址获取到,在本例中则是通过75830000+0n264获取到;

  • 读入PE头:dt ole32!_IMAGE_NT_HEADERS 75830000+0n264

接着需要在_IMAGE_OPTIONAL_HEADER可选头中找到EXPORT导出表基地址,通过PE头基址75830108加上0x018也就是OptionalHeader的偏移,即可定位到DataDirectory[0]也就是导出表基地址,其地址为75830180

根据上述定义,继续寻找EXPORT导出表的实际地址,需要注意的是Evaluate expression中的结果是根据ole32模块的基地址与VirtualAddress当前地址相加后得到的,如下图所示

当读者需要枚举特定模块时,则可通过模块基地址加上例如Name字段偏移值,来读入模块名称;

如果读者需要枚举所有导出函数,则读者可通过模块基地址加上AddressOfNames字段,并通过如下命令实现完整输出;

  • .foreach(place {dd 758e4088}){r @ t 0 = t0= t0={place}+75830000; .if(@KaTeX parse error: Expected '}', got 'EOF' at end of input: …<778e4088){da @t0}}

导入表的枚举与导出表类似,为了节约篇幅此处只给出调试数据,读者可根据自己的掌握情况自行分析学习;

# 根据模块基地址获取模块e_lfanew
0:000> dt ole32!_IMAGE_DOS_HEADER 0x75830000
   +0x000 e_magic          : 0x5a4d
   +0x028 e_res2           : [10] 0
   +0x03c e_lfanew         : 0n264

# 定位到NT头部
0:000> dt ole32!_IMAGE_NT_HEADERS 0x75830000 + 0n264
   +0x000 Signature        : 0x4550
   +0x004 FileHeader       : _IMAGE_FILE_HEADER
   +0x018 OptionalHeader   : _IMAGE_OPTIONAL_HEADER

# 基地址与e_lfanew相加得到OPTIONAL
0:000> ?0x75830000 + 0n264
Evaluate expression: 1971519752 = 75830108

# 查询OPTIONAL
0:000> dt ole32!_IMAGE_OPTIONAL_HEADER -v -ny DataDirectory 75830108+0x018
struct _IMAGE_OPTIONAL_HEADER, 31 elements, 0xe0 bytes
   +0x060 DataDirectory : [16] struct _IMAGE_DATA_DIRECTORY, 2 elements, 0x8 bytes

0:000> ? 75830108+0x018+0x60
Evaluate expression: 1971519872 = 75830180

# 得到数据目录表地址
0:000> dt ole32!_IMAGE_DATA_DIRECTORY 75830180+8
   +0x000 VirtualAddress   : 0xbd9f8
   +0x004 Size             : 0x460

0:000> ? 0x75830000+0xbd9f8
Evaluate expression: 1972296184 = 758ed9f8

# DataDirectory[1]即为导入表,地址为758ed9f8
0:000> dt ole32!_IMAGE_IMPORT_DESCRIPTOR 758ed9f8
   +0x000 Characteristics  : 0xbe700
   +0x000 OriginalFirstThunk : 0xbe700
   +0x004 TimeDateStamp    : 0
   +0x008 ForwarderChain   : 0
   +0x00c Name             : 0xbe87a
   +0x010 FirstThunk       : 0xbd8a8

0:000> da 0x75830000+0xbe87a
758ee87a  "api-ms-win-crt-string-l1-1-0.dll"

# 每一个_IMAGE_IMPORT_DESCRIPTOR的大小为0x14
0:000> ?? sizeof(_IMAGE_IMPORT_DESCRIPTOR)
unsigned int 0x14

# 也就是说,每次递增14即可输出下一个导入函数名
0:000> dt ole32!_IMAGE_IMPORT_DESCRIPTOR 758ed9f8+14
   +0x000 Characteristics  : 0xbe6f4
   +0x000 OriginalFirstThunk : 0xbe6f4
   +0x004 TimeDateStamp    : 0
   +0x008 ForwarderChain   : 0
   +0x00c Name             : 0xbe89c
   +0x010 FirstThunk       : 0xbd89c

0:000> da 0x75830000+0xbe89c
758ee89c  "api-ms-win-crt-runtime-l1-1-0.dl"

0:000> dt ole32!_IMAGE_IMPORT_DESCRIPTOR 758ed9f8+28
   +0x000 Characteristics  : 0xbe64c
   +0x000 OriginalFirstThunk : 0xbe64c
   +0x004 TimeDateStamp    : 0
   +0x008 ForwarderChain   : 0
   +0x00c Name             : 0xbeb88
   +0x010 FirstThunk       : 0xbd7f4
0:000> da 0x75830000+0xbeb88
758eeb88  "api-ms-win-crt-private-l1-1-0.dl"

# 分析第一个IID的IAT和INT
# 先看INT: IMAGE_THUNK_DATA其实就是一个DWORD,如IID一样,也是一个接一个,最后一个为NULL

第一个:
0:000> dd 0xbe6f4+0x75830000 L1
758ee6f4  000be86c

# 最高位不为1(为1表示为序号输入)指向_IMAGE_IMPORT_BY_NAME结构

.foreach(place {dd 758ee6f4}) {r @$t0 = ${place}+75830000+2; .if (@$t0<86d00000){da @$t0;}}
758ee86e  "_initterm_e"
758ee862  "_initterm"
75830002  "."
758eeb80  "memset"
758ee84e  "wcsncmp"
758ee858  "strcspn"

我们将问题回归到枚举导出表上,函数的RVA地址和名字按照顺序存放在上述两个列表中,我们可以在列表定位任意函数的RVA地址,通过与动态链接库的基地址相加得到其真实的VA,而计算的地址就是我们最终在ShellCode中调用时需要的地址,其汇编核心枚举代码如下所示;

#include <stdio.h>
#include <Windows.h>

int main(int argc, char * argv[])
{
    int a;
    __asm
    {
        mov ebx, dword ptr fs : [0x30]         ; 获取当前线程信息的地址
        mov ecx, dword ptr[ebx + 0xc]          ; 获取PEB结构体的地址
        mov ecx, dword ptr[ecx + 0x1c]         ; 获取PEB结构体中的LDR结构体的地址
        mov ecx, [ecx]                         ; 获取LDR结构体中的InMemoryOrderModuleList的头节点地址
        mov edx, [ecx + 0x8]                   ; 获取第一个模块的基址,即ntdll.dll的基址

        mov eax, [edx+0x3c]                    ; 获取PE头偏移地址
        mov ecx, [edx + eax + 0x78]            ; 获取导出表VA地址偏移
        add ecx,edx                            ; 将导出表的VA地址转换成绝对地址
        mov ebx, [ecx+0x20]                    ; 获取导出表中的导出函数名偏移数组的地址
        add ebx,edx                            ; 将函数名偏移数组的VA地址转换成绝对地址
        xor edi,edi                            ; 将edi清零,用于循环计数

    s1:
        inc edi                                ; 计数器自增1
        mov esi, [ebx+edi*4]                   ; 通过偏移获取导出函数名的地址
        add esi,edx                            ; 将导出函数名的VA地址转换成绝对地址

        cmp esi,edx                            ; 检查导出函数名的地址是否合法,如果等于基址则跳过
        je no
        loop s1                                ; 继续查找导出函数名

    no:
        xor eax,eax                            ; 清零eax寄存器,用于返回值
    }
    system("pause");
    return 0;
}

1.5.5 整合自定位ShellCode

完整的汇编代码如下,下方代码是一个定式,这里就只做了翻译,使用编译器编译如下代码。

#include <stdio.h>
#include <windows.h>

int main(int argc, char *argv)
{
    __asm
    {
        // 将索要调用的函数hash值入栈保存
            CLD                      // 清空标志位DF
            push 0x1E380A6A          // 压入MessageBoxA-->user32.dll
            push 0x4FD18963          // 压入ExitProcess-->kernel32.dll
            push 0x0C917432          // 压入LoadLibraryA-->kernel32.dll
            mov esi, esp             // 指向堆栈中存放LoadLibraryA的地址
            lea edi, [esi - 0xc]     // 后面会利用edi的值来调用不同的函数

            // 开辟内存空间,这里是堆栈空间
            xor ebx, ebx
            mov bh, 0x04       // ebx为0x400
            sub esp, ebx       // 开辟0x400大小的空间

            // 将user32.dll入栈
            mov bx, 0x3233
            push ebx           // 压入字符'32'
            push 0x72657375    // 压入字符 'user'
            push esp
            xor edx, edx        // edx=0

            // 查找kernel32.dll的基地址
            mov ebx, fs:[edx + 0x30]     // [TEB+0x30] -> PEB
            mov ecx, [ebx + 0xC]         // [PEB+0xC] -> PEB_LDR_DATA
            mov ecx, [ecx + 0x1C]        // [PEB_LDR_DATA+0x1C] -> InInitializationOrderModuleList
            mov ecx, [ecx]               // 进入链表第一个就是ntdll.dll
            mov ebp, [ecx + 0x8]         //ebp = kernel32.dll 的基地址

        // hash 的查找相关
        find_lib_functions :
                           lodsd                     // eax=[ds*10H+esi],读出来是LoadLibraryA的Hash
                           cmp eax, 0x1E380A6A       // 与MessageBoxA的Hash进行比较
                           jne find_functions        // 如果不相等则继续查找
                           xchg eax, ebp
                           call[edi - 0x8]
                           xchg eax, ebp

        // 在PE文件中查找相应的API函数
        find_functions :
        pushad
            mov eax, [ebp + 0x3C]        // 指向PE头
            mov ecx, [ebp + eax + 0x78]  // 导出表的指针
            add ecx, ebp                 // ecx=0x78C00000+0x262c
            mov ebx, [ecx + 0x20]        // 导出函数的名字列表
            add ebx, ebp                 // ebx=0x78C00000+0x353C
            xor edi, edi                 // 清空edi中的内容,用作索引

        // 循环读取导出表函数
        next_function_loop :
        inc edi                            // edi作为索引,自动递增
            mov esi, [ebx + edi * 4]       // 从列表数组中读取
            add esi, ebp                   // esi保存的是函数名称所在的地址
            cdq

        // hash值的运算过程
        hash_loop :
        movsx eax, byte ptr[esi]         // 每次读取一个字节放入eax
            cmp al, ah                   // eax和0做比较,即结束符
            jz compare_hash              // hash计算完毕跳转
            ror edx, 7
            add edx, eax
            inc esi
            jmp hash_loop
        // hash值的比较函数
        compare_hash :
        cmp edx, [esp + 0x1C]
            jnz next_function_loop         // 比较不成功则查找下一个函数
            mov ebx, [ecx + 0x24]          // ebx=序数表的相对偏移量
            add ebx, ebp                   // ebx=序数表的绝对地址
            mov di, [ebx + 2 * edi]        // di=匹配函数的序数
            mov ebx, [ecx + 0x1C]          // ebx=地址表的相对偏移量
            add ebx, ebp                   // ebx=地址表的绝对地址
            add ebp, [ebx + 4 * edi]       // 添加到EBP(模块地址库)
            xchg eax, ebp                  // 将func addr移到eax中    
            pop edi                        // edi是pushad中最后一个堆栈
            stosd
            push edi
            popad

            cmp eax, 0x1e380a6a             // 与MessageBox的hash值比较
            jne find_lib_functions

        // 下方的代码,就是我们的弹窗
        xor ebx, ebx          // 清空eb寄存器
            push ebx          // 截断字符串0

            push 0x2020206b
            push 0x72616873
            push 0x796c206f
            push 0x6c6c6568
            mov eax, esp

            push ebx          // push 0
            push eax          // push "hello lyshark"
            push eax          // push "hello lyshark"
            push ebx          // push 0
            call[edi - 0x04]  // call MessageBoxA

            push ebx          // push 0
            call[edi - 0x08]  // call ExitProcess
    }
    return 0;
}

运行后会弹出一个提示框hello lyshark说明我们成功了,此列代码就是所谓的自定位代码,该代码可以不依赖于系统环境而独立运行;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/713847.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

提取图像中的文本信息(Tesseract OCR 和 pytesseract)

环境准备 安装Tesseract&#xff1a;点这里参考本人博客 下载第三方库 pip install Pytesseract这个库只自带了一个英语的语言包&#xff0c;这个时候如果我们图片中有对中文或者其他语言的识别需求&#xff0c;就需要去下载其他语言包 下载其他语言包 进入官网以后进入Tra…

MyBatisPlus基础知识

一、MyBatisPlus 1.MyBatisPlus入门案例与简介 这一节我们来学习下MyBatisPlus的入门案例与简介&#xff0c;这个和其他课程都不太一样&#xff0c;其他的课程都是先介绍概念&#xff0c;然后再写入门案例。而对于MyBatisPlus的学习&#xff0c;我们将顺序做了调整&#xff0…

STM32——MCU简单介绍

文章目录 一、单片机基础简介1.MCU简介&#xff08;1&#xff09;MCU的组成&#xff08;2&#xff09;常见的MCU 2.STM32简介&#xff08;1&#xff09;STM32&STM8产品型号--各个字母的含义 3.如何查手册&#xff08;1&#xff09;数据手册芯片信息总线框图时钟树内存映射 …

Java面试题及答案整理( 金九银十最新版,持续更新)

最近可能有点闲的慌&#xff0c;没事就去找面试面经&#xff0c;整理了一波面试题。我大概是分成了 Java 基础、中级、高级&#xff0c;分布式&#xff0c;Spring 架构&#xff0c;多线程&#xff0c;网络&#xff0c;MySQL&#xff0c;Redis 缓存&#xff0c;JVM 相关&#xf…

【vue】使用uni-indexed-list组件点击获取下标详情

问题描述 使用uniapp自带的uni-indexed-list组件&#xff0c;点击索引只能获取到点击的名称&#xff0c;不能获取其他信息 解决方案&#xff1a; uni-indexed-list组件1.2.1版本 对uni-indexed-list组件代码进行修改,示例如下: 找到setList函数&#xff0c;对内部逻辑赋值就…

二叉树 — 多叉转二叉树

题目&#xff1a; 将一棵多叉树&#xff0c;转换成二叉树&#xff0c;在通过这个二叉树还原成多叉树。 分析 毫无疑问&#xff0c;多叉树的头结点也是转换的二叉树的头结点。 多叉树如下图所示&#xff1a; 转换成二叉树&#xff0c;则将多叉树所有的节点X&#xff0c;将X的孩…

MATLAB导入EXCEL表格数据画散点图

namexxxx.xlsx;%这里的xxxx是EXCEL文件的名字&#xff0c;而且需要将它和.m文件放在同一个文件夹下 axlsread(name,D2:D25); aa; bxlsread(name,I2:I25); bb; x[a,b]; cxlsread(name,E2:E25); cc; dxlsread(name,J2:J25); dd; y[c,d]; plot(x,y,b-o),grid on;%b-o是颜色和图案&…

linux 内核接口atomic_long_try_cmpxchg_acquire/release详解

linux 内核接口atomic_long_try_cmpxchg_acquire详解 1 atomic_long_try_cmpxchg_acquire/release1.1 atomic_long_try_cmpxchg_acquire1.2 atomic_long_try_cmpxchg_release 2 arch_atomic64_cmpxchg_acquire/release2.1 arch_atomic64_cmpxchg_acquire/release定义2.2 atomic…

移远通信携手中国电信等伙伴重磅发布5G NTN试验成果,共促卫星物联网产业发展

6月29日&#xff0c;在MWC上海展期间&#xff0c;以“5G云网新科技 数字经济新动能”为主题的2023中国电信5G/6G科技创新成果发布会顺利举行。 会上&#xff0c;中国电信联合合作伙伴重磅发布了多项科技创新成果和科技创新应用&#xff0c;作为中国电信在卫星物联网领域重要的合…

ArcGIS SDE空间数据库 镶嵌数据集白边压盖及不显示问题

首先&#xff0c;在Oracle SDE空间数据库中新建了镶嵌数据集(Mosaic Dataset) &#xff0c;这里通过程序导入影像数据以后出现了 影像不显示&#xff08;得放到很小比例尺才显示&#xff09;及影像之间互相压盖 第一&#xff0c;解决影像互相压盖问题 在Calalog中右键镶嵌数据…

操作系统6——文件管理

本系列博客重点在深圳大学操作系统课程的核心内容梳理&#xff0c;参考书目《计算机操作系统》&#xff08;有问题欢迎在评论区讨论指出&#xff0c;或直接私信联系我&#xff09;。 梗概 本篇博客主要介绍操作系统第七章文件管理和第八章磁盘储存器的管理的相关知识。 目录 …

智谱AI-算法实习生(知识图谱方向)实习面试记录

岗位描述 没错和我的经历可以说是match得不能再match了&#xff0c;但是还是挂了hh。 面试内容 给我面试的是唐杰老师的博士生&#xff0c;方向是社交网络数据挖掘&#xff0c;知识图谱。不cue名了&#xff0c;态度很友好的 &#xff0c;很赞。 date&#xff1a;6.28 Q1 自…

【算法之双指针I】leetcode344.反转字符串

344.反转字符串 力扣题目链接 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配额外的空间&#xff0c;你必须**原地修改输入数组**、使用 O(1) 的额外空间解决这一问题。 输入&#xff1a;s ["h…

【代理服务器】Squid代理服务器应用

目录 一、Squid代理服务器1.1代理的工作机制1.2代理服务器的概念1.3代理服务器的作用1.4Squid 代理的类型 二、安装Squid服务2.1编译安装Squid2.2修改 Squid 的配置文件2.3Squid运行控制2.4创建Squid服务脚本2.5 构建传统代理服务器2.6更改防火墙规则2.7验证 三、构建透明代理服…

Keil MDK 5 仿真STM32F4报错no ‘read‘ permission

问题描述 MDK软件模拟仿真提示没有读写权限&#xff0c;只能单步运行。error提示&#xff1a; *** error 65: access violation at 0x40023C00 : no read permission 关于Keil MDK 5 仿真STM32F4报错no ‘read’ permission的解决方法 Vision 调试器为所有 ARM7、ARM9、Corte…

怎样在文章末尾添加尾注(将尾注的数字变为方括号加数字)

在进行文章编写或者需要添加注解时&#xff0c;需要进行尾注的添加&#xff0c;下面将详细说明如何进行尾注的添加 操作 首先打开需要进行添加尾注的文档&#xff0c;将光标移动至需要进行添加尾注的文字后。 紧接着在上方工具栏中&#xff0c;选择引用&#xff0c;在引用页…

4.FreeRTOS系统配置文件详解(FreeRTOSConfig.h)

目录 一、基础配置选项 二、内存分配相关定义 三、钩子函数的相关定义 四、运行时间和任务状态统计相关定义 五、软件定时器相关配置 FreeRTOSConfig.h配置文件的作用&#xff1a; 对FreeRTOS进行功能配合和裁剪&#xff0c;以及API函数的使能 对于FreeRTOS配置文件主要…

如果制作投票选举投票制作制作一个投票在线制作投票

用户在使用微信投票的时候&#xff0c;需要功能齐全&#xff0c;又快捷方便的投票小程序。 而“活动星投票”这款软件使用非常的方便&#xff0c;用户可以随时使用手机微信小程序获得线上投票服务&#xff0c;很多用户都很喜欢“活动星投票”这款软件。 “活动星投票”小程序在…

CORS如何实现跨域(前端+后端代码实例讲解)

书接上回&#xff0c;上一篇文章讲解了用 jsonp 来解决跨域问题&#xff0c;这篇文章讲解另外一种方法也可以解决跨域问题&#xff0c;那就是CORS&#xff08;跨源资源共享&#xff09;。 什么是CORS&#xff1f; 下面是官方的解释&#xff1a;跨源资源共享&#xff08;CORS&a…

刚去了家新公司,发现个个都是卷王 , 想离职了。。

个个都说想躺平了&#xff0c;可是有一说一&#xff0c;该卷的还是卷。这不&#xff0c;前段时间我们公司来了个00后&#xff0c;才工作一年&#xff0c;跳槽到我们公司起薪15K&#xff0c;都快接近我了。后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了。…