1.4 编写简易ShellCode弹窗

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

在前面的章节中相信读者已经学会了使用Metasploit工具生成自己的ShellCode代码片段了,本章将继续深入探索关于ShellCode的相关知识体系,ShellCode 通常是指一个原始的可执行代码的有效载荷,攻击者通常会使用这段代码来获得被攻陷系统上的交互Shell的访问权限,而现在用于描述一段自包含的独立的可执行代码片段。ShellCode代码的编写有多种方式,通常会优先使用汇编语言实现,这得益于汇编语言的可控性。

ShellCode 通常会与漏洞利用并肩使用,或是被恶意代码用于执行进程代码的注入,通常情况下ShellCode代码无法独立运行,必须依赖于父进程或是Windows文件加载器的加载才能够被运行,本章将通过一个简单的弹窗(MessageBox)来实现一个简易版的弹窗功能,并以此来加深读者对汇编语言的理解。

1.4.1 寻找DLL库函数地址

在编写ShellCode之前,我们需要查找一个函数地址,由于我们需要调用MessageBoxA()这个函数,所以需要获取该函数的内存动态地址,根据微软的官方定义可知,该函数默认放在了User32.dll库中,为了能够了解压栈时需要传入参数的类型,我们还需要查询一下函数的原型;

在微软定义中MessageBoxA函数的原型如下:

int MessageBoxA(
  HWND hWnd,
  LPCSTR lpText,
  LPCSTR lpCaption,
  UINT uType
);

参数说明:

  • hWnd:消息框的父窗口句柄。
  • lpText:消息框中显示的文本。
  • lpCaption:消息框的标题栏文本。
  • uType:消息框的类型,可以指定消息框包含的按钮以及图标等。

需要注意的是,由于我们调用的是MessageBoxA,而此函数为ASCII模式,需要读者自行修改解决方案,在配置属性的常规选项卡,修改字符集(使用多字节字符集)即可,如下图所示;

1.4 编写简易ShellCode弹窗

读者可以通过编写一段简单的代码来获取所需数据,首先通过LoadLibrary函数加载名为user32.dll的动态链接库,并将其基地址存储在HINSTANCE类型的变量LibAddr中。然后,使用GetProcAddress函数获取 MessageBoxA函数的地址,并将其存储在MYPROC类型的变量ProcAddr中。最后输出所需结果;

#include <windows.h>
#include <iostream>

typedef void(*MYPROC)(LPTSTR);

int main(int argc, char *argv[])
{
    HINSTANCE LibAddr,KernelAddr;
    MYPROC ProcAddr;

    // 获取User32.dll基地址
    LibAddr = LoadLibrary("user32.dll");
    printf("user32.dll 动态库基地址 = 0x%x \n", LibAddr);

    // 获取kernel32.dll基地址
    KernelAddr = LoadLibrary("kernel32.dll");
    printf("kernel32.dll 动态库基地址 = 0x%x \n", KernelAddr);

    // 获取MessageBox基地址
    ProcAddr = (MYPROC)GetProcAddress(LibAddr, "MessageBoxA");
    printf("MessageBoxA 函数相对地址 = 0x%x \n", ProcAddr);

    // 获取ExitProcess基地址
    ProcAddr = (MYPROC)GetProcAddress(KernelAddr, "ExitProcess");
    printf("ExitProcess 函数相对地址 = 0x%x \n", ProcAddr);

    system("pause");
    return 0;
}

上方的代码经过编译运行后会得到两个返回结果,如下图所示,其中User32.dll的基地址是0x75a40000而该模块内的MessageBoxA函数在当前系统中的地址为0x75ac0ba0,当然这两个模块地址在每次系统启动时都会发生幻化,读者电脑中的地址肯定与笔者不相同,这都是正常现象,之所以会出现这种情况是因为,系统中存在一种ASLR机制。

扩展知识:ASLR(Address Space Layout Randomization)机制的核心是用于随机化系统中程序和数据的内存地址分布,从而增加攻击者攻击系统的难度,在启用了ASLR机制的系统下,每次运行程序时,程序和系统组件(例如DLL、驱动程序等)都会被分配不同的内存地址,而不是固定的内存地址。这样可以使得攻击者难以利用已知的内存地址漏洞进行攻击,因为攻击者需要先找到正确的内存地址才能利用漏洞。ASLR的随机化是根据操作系统的一些随机因素进行计算的,例如启动时间、进程 ID 等等。

由于如上机制的存在,导致user32.dll模块地址不确定,也就会导致其地址内部的API函数地址也会发生一定的变化,下图仅作为参考图;

1.4 编写简易ShellCode弹窗

在获取到MessageBoxA函数的内存地址以后,我们接着需要获取一个ExitProecess函数的地址,这个API函数的作用是让程序正常退出,这是因为我们注入代码以后,原始的堆栈地址会被破坏,堆栈失衡后会导致程序崩溃,所以为了稳妥起见我们还是添加一行正常退出为好。函数ExitProcess的原型如下:

VOID WINAPI ExitProcess(
  UINT uExitCode
);

其中参数uExitCode指定了进程的退出代码,表示进程成功退出或者发生了错误。如果uExitCode为0,表示进程成功退出,其他的非0值则表示进程发生了错误,不同的非0值可以用于表示不同的错误类型。

1.4.2 探讨STDCALL调用约定

既然获取到了相应的内存地址,那么接下来就需要通过汇编来编写可执行代码片段了,在编写这段代码之前,先来了解一下汇编语言的调用约定,在汇编语言中,要想调用某个函数,需要使用CALL语句,而在CALL语句的后面,要跟上该函数在系统中的地址,前面我们已经获取到了相应的内存地址了,所以在这里就可以通过CALL相应的地址来调用相应的函数。

我们以32位应用程序为例,在32位应用程序内通常使用STDCALL调用约定,它定义了函数在被调用时,参数传递、返回值传递以及栈的使用等方面的规则,该调用约定的规则如下所示:

  • 参数传递:参数从右向左依次压入栈中,由被调用者在返回前清理栈。
  • 返回值传递:函数返回时将返回值存储在EAX寄存器中。
  • 栈的使用:函数被调用前,调用者将参数压入栈中;被调用者在返回前清理栈,以确保栈的平衡。
  • 函数调用:在调用函数之前,调用者将返回地址(Return Address)和EBP寄存器的值保存在栈中,并将ESP寄存器指向参数列表的最后一个元素;在函数返回之后,调用者通过将之前保存的EBP和返回地址弹出栈中,并将ESP寄存器恢复到最初的位置来恢复栈的状态。

总之,stdcall调用约定将参数按照从右到左的顺序压入栈中,由被调用者清理栈,返回值存储在EAX寄存器中,函数调用者和被调用者都需要遵循一定的栈使用规则。这种约定的好处是参数传递简单,可读性高,并且在函数返回时栈已经被清理,不需要额外的清理工作。

在实际的编程中,一般还是先将地址赋值给eax寄存器,然后再CALL调用相应的寄存器实现调用,比如现在笔者有一个lyshark(a,b,c,d)函数,如果我们想要调用它,那么它的汇编代码就应该编写为:

push d
push c
push b
push a
mov eax,AddressOflyshark    // 获取偏移地址
call eax                    // 间接调用

根据上方的调用方式,我们可以写出ExitProcess()函数的汇编版调用结构,如下;

xor ebx, ebx
push ebx
mov eax, 0x76c84100
call eax

接着编写MessageBox()这个函数调用。与ExitProcess()函数不同的是,这个API函数包含有四个参数,当然第一和第四个参数,我们可以赋给0值,但是中间两个参数都包含有较长的字符串,这个该如何解决呢?我们不妨先把所需要用到的字符串转换为ASCII码值,转换的方式有许多,如下代码则是通过Python实现的转换模式;

import os,sys
from LyScript32 import MyDebug

# 字符串转ascii
def StringToAscii(string):
    ref = []
    for index in range(0,len(string)):
        hex_str = str(hex(ord(string[index])))
        ref.append(hex_str.replace("0x","\\x"))
    return ref

if __name__ == "__main__":

    # 输出MsgBox标题
    title = StringToAscii("alert")
    for index in range(0,len(title)):
        print(title[index],end="")

    print()
    # 输出MsgBox内容
    box = StringToAscii("hello lyshark")
    for index in range(0,len(box)):
        print(box[index],end="")

Python程序被运行,则用户即可得到两串通过编码后的字符串数据。

MsgBox标题:alert              \x61\x6c\x65\x72\x74\x21
MsgBox内容:hello lyshark      \x68\x65\x6c\x6c\x6f\x20\x6c\x79\x73\x68\x61\x72\x6b

由于我们使用的是32位汇编,所以上方的字符串需要做一定的处理,我们分别将每四个字符为一组,进行分组,将不满四个字符的,以空格0x20进行填充,这是因为我们采用的存储字符串模式为栈传递,而一个寄存器为32位,所以就需要填充满4字节才可以平衡;

-------------------------------------------------------------
填充 alert
-------------------------------------------------------------
\x61\x6c\x65\x72
\x74\x21\x20\x20

-------------------------------------------------------------
填充 hello lyshark
-------------------------------------------------------------
\x68\x65\x6c\x6c
\x6f\x20\x6c\x79
\x73\x68\x61\x72
\x6b\x20\x20\x20

上方的空位置之所以需要以0x20进行填充,而不是0x00进行填充,是因为strcpy这个字符串拷贝函数,默认只要一遇到0x00就会认为我们的字符串结束了,就不会再拷贝0x00后的内容了,所以这里就不能使用0x00进行填充了,这里要特别留意一下。

接着我们需要将这两段字符串分别压入堆栈存储,这里需要注意,由于我们的计算机是小端序排列的,因此字符的入栈顺序是从后往前不断进栈的,上面的字符串压栈参数应该写为:

小提示:小端序(Little Endian)是一种数据存储方式,在汇编语言中,小端序的表示方式与高位字节优先(Big Endian)相反。例如,对于一个16位的整数0x1234,它在小端序的存储方式下,将会被存储为0x340x12(低位字节先存储);而在高位字节优先的存储方式下,将会被存储为0x120x34(高位字节先存储)。

-------------------------------------------------------------
压入字符串 alert
-------------------------------------------------------------
push 0x20202174
push 0x72656c61

-------------------------------------------------------------
压入字符串 hello lyshark
-------------------------------------------------------------
push 0x2020206b
push 0x72616873
push 0x796c206f
push 0x6c6c6568

既然字符串压入堆栈的功能有了,那么下面问题来了,我们如何获取这两个字符串的地址,从而让其成为MessageBox()的参数呢?

其实这个问题也不难,我们可以利用esp指针,因为它始终指向的是栈顶的位置,我们将字符压入堆栈后,栈顶位置就是我们所压入的字符的位置,于是在每次字符压栈后,可以加入如下指令,依次将第一个字符串基地址保存至eax寄存器中,将第二个基地址保存至ecx寄存器中。

xor ebx,ebx                 // 清空寄存器
push 0x20202174             // 字符串 alert 
push 0x72656c61
mov eax,esp                 // 获取第一个字符串的地址

push ebx                    // 压入00为了将两个字符串分开

push 0x2020206b             // 字符串 hello lyshark
push 0x72616873
push 0x796c206f
push 0x6c6c6568
mov ecx,esp                 // 获取第二个字符串的地址

上方汇编指令完成压栈以后,接下来我们就可以调用MessageBoxA函数了,其调用代码如下。

push ebx                             // push 0
push eax                             // push "alert"
push ecx                             // push "hello lyshark !"
push ebx                             // push 0
mov eax,0x75ac0ba0                   // 将MessageBox地址赋值给EAX
call eax                             // 调用 MessageBox

1.4.3 ShellCode提取与应用

通过上方的实现流程,我们的ShellCode就算开发完成了,接下来读者只需要将上方ShellCode整理成一个可执行文件并编译即可。

#include <iostream>

int main(int argc, char *argv[])
{
    _asm
    {
        sub esp, 0x50          // 抬高栈顶,防止冲突
        xor ebx, ebx           // 清空ebx
        push ebx
        push 0x20202174
        push 0x72656c61        // 字符串 "alert"
        mov eax, esp           // 获取栈顶
        push ebx               // 填充00 截断字符串

        push 0x2020206b
        push 0x72616873
        push 0x796c206f
        push 0x6c6c6568         // 字符串 hello lyshark
        mov ecx, esp            // 获取第二个字符串的地址

        push ebx
        push eax
        push ecx
        push ebx
        mov eax, 0x75ac0ba0    // 获取MessageBox地址
        call eax               // call MessageBox

        push ebx
        mov eax, 0x76c84100   // 获取ExitProcess地址
        call eax              // call ExitProcess
    }
    return 0;
}

接下来就是需要手动提取此处汇编指令的特征码,本案例中我们可以通过x64dbg中的LyScript插件实现提取,首先载入被调试进程,然后寻找到如下所示的特征位置,当遇到Call时,则通过F7进入到内部,如下图所示;

1.4 编写简易ShellCode弹窗

如下图中所示,就是我们所需要的汇编指令集,也就是我们自己的ShellCode代码片段,内存地址为0x002D12A0转换为十进制为2953888

1.4 编写简易ShellCode弹窗

通过LyScript插件并编写如下脚本,并将EIP位置设置为eip = 2953888运行这段代码;

from LyScript32 import MyDebug

if __name__ == "__main__":
    dbg = MyDebug()
    dbg.connect()
    ShellCode = []
    eip = 2953888

    for index in range(0, 100 - 1):
        read_code = dbg.read_memory_byte(eip + index)
        ShellCode.append(str(hex(read_code)))

    for index in ShellCode:
        print(index.replace("0x","\\x"),end="")
    dbg.close()

则可输出如下图所示的完整特征码,读者可自行将此处特征码格式化;

1.4 编写简易ShellCode弹窗

当然读者通过在_asm指令位置设置F9断点,并通过F5启动调试,如下图所示;

1.4 编写简易ShellCode弹窗

当调试器被断下时,通过按下Ctrl+Alt+D跳转至反汇编代码位置,并点击显示代码字节,同样可以实现提取,如下图所示;

1.4 编写简易ShellCode弹窗

我们直接将上方的这些机器码提取出来,从而编写出完整的ShellCode,最终测试代码如下。

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

#pragma comment(linker,"/section:.data,RWE")

unsigned char shellcode[] = "\x83\xec\x50"
"\x33\xdb"
"\x53"
"\x68\x74\x21\x20\x20"
"\x68\x61\x6c\x65\x72"
"\x8b\xc4"
"\x53"
"\x68\x6b\x20\x20\x20"
"\x68\x73\x68\x61\x72"
"\x68\x6f\x20\x6c\x79"
"\x68\x68\x65\x6c\x6c"
"\x8b\xcc"
"\x53"
"\x50"
"\x51"
"\x53"
"\xb8\xa0\x0b\xac\x75"
"\xff\xd0"
"\x53"
"\xb8\x00\x41\xc8\x76"
"\xff\xd0";

int main(int argc, char **argv)
{
    LoadLibrary("user32.dll");
    __asm
    {
        lea eax, shellcode
        call eax
    }
    return 0;
}

上方代码经过编译以后,运行会弹出一个我们自己DIYMessageBox提示框,输出效果图如下所示;

1.4 编写简易ShellCode弹窗文章来源地址https://www.toymoban.com/news/detail-514046.html

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

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

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

相关文章

  • [渗透测试]—7.1 漏洞利用开发和Shellcode编写

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

    2024年02月11日
    浏览(46)
  • 网络攻防技术-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日
    浏览(40)
  • uni-app弹窗列表滚动, 弹框下面的内容也跟随滚动解决方案

    滑动弹窗里的列表,弹框下面的内容也会跟着滑动,导致弹窗中的列表不能正常滚动 1.弹窗组件代码,需要在最外层的view中加入@touchmove.stop.prevent=\\\"moveHandle\\\",且弹窗中需要滚动的列表要使用scroll-view标签包裹起来,且scroll-y 属性不能忘记加。  

    2024年02月12日
    浏览(53)
  • Scala第二十章节(Akka并发编程框架、Akka入门案例、Akka定时任务代码实现、两个进程间通信的案例以及简易版spark通信框架案例)

    章节目标 理解Akka并发编程框架简介 掌握Akka入门案例 掌握Akka定时任务代码实现 掌握两个进程间通信的案例 掌握简易版spark通信框架案例 1. Akka并发编程框架简介 1.1 Akka概述 Akka是一个用于构建高并发、分布式和可扩展的基于事件驱动的应用工具包。Akka是使用scala开发的库,

    2024年04月11日
    浏览(42)
  • Linux编写简易shell

    思路:​ ​ ​ 所以要写一个shell,需要循环以下过程:​ 获取命令行 解析命令行 建立一个子进程(fork) 替换子进程(execvp) 父进程等待子进程退出(wait) 实现代码:​         以上就是本文的全部内容,如果对你有帮助,欢迎点赞收藏转发评论! 

    2024年01月20日
    浏览(34)
  • 编写一个简易的 Axios 函数

    编写一个简易的 Axios 函数:从零开始创建你自己的网络请求工具 当我们开始构建自己的网络请求工具时,不禁思考着:在现代的网络开发中,Axios等工具库如此受欢迎,其背后的原理是什么?这篇文章将带你踏上一个旅程,逐步构建一个简单但功能强大的 Axios 类型函数。 预

    2024年02月04日
    浏览(63)
  • 用python简易编写创建窗口

    # 创建Window窗口.py # 窗口属性 # 显示窗口(消息循环) 代码如下:  

    2024年02月11日
    浏览(42)
  • 前端——编写一个简易网页计算器

    如下图效果所示,输入两个运算数,点击不同的运算符,会在下方得到不同的运算结果 分析与代码实现 在HTML部分,定义了一个标题为\\\"网页计算器\\\"的网页,并创建了两个输入框和四个按钮。最后,创建了一个只读的结果显示框,便于计算结果的输出 在JavaScript部分,定义了一

    2024年01月24日
    浏览(52)
  • 利用python编写简易POC脚本

    POC: 概念证明,即概念验证(英语:Proof of concept,简称POC)是对某些想法的一个较短而不完整的实现,以证明其可行性,示范其原理,其目的是为了验证一些概念或理论。  声明:请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与文章作者和本博客无

    2024年02月21日
    浏览(53)
  • 手写简易操作系统(十七)--编写键盘驱动

    上一节我们实现了锁与信号量,这一节我们就可以实现键盘驱动了,访问键盘输入的数据也属于临界区资源,所以需要锁的存在。 之前的 ps/2 键盘使用的是中断驱动的,在当时,按下键盘就会触发中断,引导操作系统去处理这个按键行文。但是当今的usb键盘,使用的是轮询机

    2024年04月26日
    浏览(41)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包