1.5 编写自定位ShellCode弹窗

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

在笔者上一篇文章中简单的介绍了如何运用汇编语言编写一段弹窗代码,虽然简易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内核自动创建和管理,可以通过系统调用和调试器工具来访问和修改其内容。

1.5 编写自定位ShellCode弹窗

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

1.5 编写自定位ShellCode弹窗

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

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

1.5 编写自定位ShellCode弹窗

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

1.5 编写自定位ShellCode弹窗

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

1.5 编写自定位ShellCode弹窗

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

1.5 编写自定位ShellCode弹窗

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

1.5 编写自定位ShellCode弹窗

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

1.5 编写自定位ShellCode弹窗

既然有了如上所述的方法,那么读者可以很容易的实现这段功能,为了便于读者理解,笔者先提供一段使用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;
}

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

1.5 编写自定位ShellCode弹窗

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

    .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 ) );

1.5 编写自定位ShellCode弹窗

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

1.5 编写自定位ShellCode弹窗

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

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

1.5 编写自定位ShellCode弹窗

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

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

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

1.5 编写自定位ShellCode弹窗

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

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

1.5 编写自定位ShellCode弹窗

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

1.5 编写自定位ShellCode弹窗

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

1.5 编写自定位ShellCode弹窗

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

1.5 编写自定位ShellCode弹窗

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

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 编写自定位ShellCode弹窗

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值,其输出效果如下图所示;

1.5 编写自定位ShellCode弹窗

在理解了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

1.5 编写自定位ShellCode弹窗

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

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

1.5 编写自定位ShellCode弹窗

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

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

1.5 编写自定位ShellCode弹窗

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

1.5 编写自定位ShellCode弹窗

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

1.5 编写自定位ShellCode弹窗

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

1.5 编写自定位ShellCode弹窗

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

  • .foreach(place {dd 758e4088}){r @\(t0=\){place}+75830000; .if(@\(t0<778e4088){da @\)t0}}

1.5 编写自定位ShellCode弹窗

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

# 根据模块基地址获取模块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说明我们成功了,此列代码就是所谓的自定位代码,该代码可以不依赖于系统环境而独立运行;

1.5 编写自定位ShellCode弹窗文章来源地址https://www.toymoban.com/news/detail-515722.html

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

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

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

相关文章

  • 一篇文章彻底清楚shellcode(精品)

    1.没开沙箱(此时我们可以系统调用get shell) (一)32位程序系统调用 32位程序有别于64位程序,32位通过栈传参,我们常用的寄存器有4个数据寄存器(eax,ebx,ecx,edx),2个变址寄存器(esi,edi),2个指针寄存器(esp,ebp). 下边我们就来看一种系统调用方式及其构造: 执行上述shellcode即可g

    2024年02月09日
    浏览(43)
  • 1.6 编写双管道ShellCode后门

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

    2024年02月12日
    浏览(224)
  • 1.8 运用C编写ShellCode代码

    在笔者前几篇文章中,我们使用汇编语言并通过自定位的方法实现了一个简单的 MessageBox 弹窗功能,但由于汇编语言过于繁琐在编写效率上不仅要考验开发者的底层功底,还需要写出更多的指令集,这对于普通人来说是非常困难的,当然除了通过汇编来实现 ShellCode 的编写以

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

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

    2024年02月11日
    浏览(48)
  • 网络攻防技术-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)
  • MySQL入门阶段这一篇就够了-学习笔记(手敲1.5万字)

    虽然在大一下学期,就已经接触到了MySQL,但是那个时候只是会用MySQL进行增删改查,在大三上学期,尝试投简历寻找实习时,对方公司对于程序员的MySQL水平有很高的要求,所以我开始系统化的学习MySQL。顺便将整理出的笔记逐步写入博客中,日积月累,准备发表一篇长篇博

    2024年02月15日
    浏览(43)
  • 文本----简单编写文章的方法(中),后端接口的编写,自己编写好页面就上传到自己的服务器上,使用富文本编辑器进行编辑,想写好一个项目,先分析一下需求,再理一下实现思路,再搞几层,配好参数校验,lomb

    1.1 今天在编写代码的时候,突然想实现一个目标:怎样能够在自己的网站上发一些文章  (lingyidianke.com) 1.2 参考自己之前写的一些资料,做一做试试,那么怎么做呢?首先,我们参考一下我们之前的资料,之前写过的大事件资料: 1.2.1 从项目结构上看,我们要创两个项目 1

    2024年02月19日
    浏览(52)
  • OpenCV这么简单为啥不学——1.5、解决putText中文乱码问题

    目录 OpenCV这么简单为啥不学——1.5、解决putText中文乱码问题 前言 putText中文乱码问题 putText中文乱码解决方案 中文实例 总结 计算机视觉市场巨大而且持续增长,且这方面没有标准API,如今的计算机视觉软件大概有以下三种: 1、研究代码(慢,不稳定,独立并与其他库不兼

    2024年02月02日
    浏览(35)
  • python selenium 定位鼠标悬浮后的新弹窗数据

            最近需要获取网页上的标签数据,但是标签大于3个以后是隐藏的,需要鼠标hover上去才显示。如下图,图一是刚进来界面展示的,需要知道额外的7个标签则需要将鼠标移动到目标上面去。            但是比较尴尬的一个点是,当游览器打开F12后,使用鼠标去选中关

    2024年02月06日
    浏览(44)
  • 一篇关于计算机网络原理的文章

    计算机网络原理是指计算机网络的基本概念、协议以及通信技术等方面的知识。 了解计算机网络原理对于我们使用互联网、编程开发以及网络安全等方面都极为重要。 在本文中,将介绍计算机网络原理的基本内容。 一、计算机网络的基本概念 计算机网络是指将分布在不同地

    2023年04月23日
    浏览(93)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包