1.4 编写简易ShellCode弹窗

news2024/10/7 6:44:09

在前面的章节中相信读者已经学会了使用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模式,需要读者自行修改解决方案,在配置属性的常规选项卡,修改字符集(使用多字节字符集)即可,如下图所示;

读者可以通过编写一段简单的代码来获取所需数据,首先通过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函数地址也会发生一定的变化,下图仅作为参考图;

在获取到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进入到内部,如下图所示;

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

通过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()

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

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

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

我们直接将上方的这些机器码提取出来,从而编写出完整的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提示框,输出效果图如下所示;

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/f7242d3c.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

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

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

相关文章

市值暴跌后,每日优鲜能否靠2亿融资“续命”?

濒临破产退市的每日优鲜&#xff0c;靠转型实现“自救”&#xff1f; 作为“生鲜电商第一股”&#xff0c;每日优鲜在上市1年后爆发生存危机。 8月4日&#xff0c;每日优鲜(NDAQ:MF)公布了2022年报&#xff0c;尽管去年7月其宣布关闭营收占比约90%的DWM业务&#xff0c;全面终…

每日一题——柱状图中最大的矩形

柱状图中最大的矩形 题目链接 用什么数据结构&#xff1f; 要得到柱状图中最大的矩形&#xff0c;我们就必须要知道对于每一个高度heights[i]&#xff0c;他所能勾勒出的矩形最大是多少&#xff08;即宽度最大是多少&#xff09;。 而对应到图上我们可以知道&#xff0c;要知…

vue naive ui 按钮绑定按键

使用vue (naive ui) 绑定Enter 按键 知识点: 按键绑定Button全局挂载使得message,notification, dialog, loadingBar 等NaiveUI 生效UMD方式使用vue 与 naive ui将vue默认的 分隔符大括号 替换 为 [[ ]] <!DOCTYPE html> <html lang"en"> <head>…

助力养殖行业数字化转型,基于深度学习模型开发构建牛脸识别系统

在我们以往接触到的项目或者是业务场景中&#xff0c;大多牵涉到生物特征识别的任务基本都是人脸识别&#xff0c;这也是目前我们每天都会接触到的应用&#xff0c;比如&#xff1a;上下班的打卡、支付时的刷脸等等&#xff0c;这也是比较成熟的一项AI应用。 这里我们简单对一…

用智能汽车完成自救,高通的光明与暗夜!

这几年国产汽车的在智驾驶的发力&#xff0c;无疑受到了各行各业的瞩目&#xff0c;汽车智能化已经逐步走进大众视野。 我们之前阐述过高通在3C数码领域的见解与财报预测&#xff0c;随着第三财季的业绩报告显示&#xff0c;可以看到的是智能手机、IoT 市场的低迷让高通的营收…

C#调用barTender打印标签示例

使用的电脑需要先安装BarTender 我封装成一个类 using System; using System.Windows.Forms;namespace FT_Tools {public class SysContext{public static BarTender.Application btapp new BarTender.Application();public static BarTender.Format btFormat;public void Q…

vivado复制工程后如何修改路径

在 vivado 工程复制之后&#xff0c;直接打开项目&#xff0c;会发现所有文件都指向原工程&#xff0c;这个问题困扰了我好久&#xff08;之前都是项目中 remove 之后再一个一个重新添加&#xff0c;特别麻烦&#xff09;。然而前几天突发奇想试着把除 .srcs 文件夹之外的所有文…

大规模数据爬取 - 增量和分布式爬虫架构实战

嗨&#xff0c;亲爱的爬虫开发者们&#xff01;在当今的数据驱动时代&#xff0c;大规模数据的爬取对于许多领域的研究和应用至关重要在本文中&#xff0c;我将与你分享大规模数据爬取的实战经验&#xff0c;重点介绍增量和分布式爬虫架构的应用&#xff0c;帮助你高效地处理海…

AUTOSAR DEM (一):简介

AUTOSAR DEM &#xff08;一&#xff09;:简介 故障事件触发故障信息上报故障信息处理故障事件存储DEM与其他模块的联系 缩略词说明 abbreviationdescriptionDEMDiagnostic event managerDTCDiagnostic Trouble CodeBSWBasic softwareSWCSoftware componenECUMECU state manag…

Java 大厂面试 —— 常见集合篇 List HashMap 红黑树

23Java面试专题 八股文面试全套真题&#xff08;含大厂高频面试真题&#xff09;多线程_软工菜鸡的博客-CSDN博客 常见集合篇-01-集合面试题-课程介绍 02-算法复杂度分析 2 List相关面试题 2.1 数组 2.1.1 数组概述 数组&#xff08;Array&#xff09;是一种用连续的内存空…

Win11 重启资源管理器的方法

方法一&#xff1a;按【Ctrl Alt Del】组合键后&#xff0c;调出锁定界面&#xff0c;然后点击【任务管理器】即可 方法二&#xff1a;按【Ctrl Shift ESC】组合键后&#xff0c;会直接调出任务管理器 1、在任务管理器窗口中&#xff0c;找到名称为【Windows 资源管理器】…

使用synchronized关键字同步类方法

要想解决“脏数据”的问题&#xff0c;最简单的方法就是使用synchronized关键字来使run方法同步&#xff0c;代码如下&#xff1a; public synchronized void run() { } 从上面的代码可以看出&#xff0c;只要在void和public之间加上synchronized关键字&#xff0c;就可以…

内网远程控制总结

前言 在内网渗透过程中&#xff0c;会碰到远程控制soft或者其他&#xff0c;这里针对远程控制软件做如下总结。 远程控制软件 向日葵篇 向日葵查看版本 向日葵&#xff08;可以攻击&#xff09; 针对向日葵的话其实如果有本地安装的话&#xff0c;是有可能存在漏洞的。这…

[CKA]考试注意事项及作者考试结果

在CKA考试的时候&#xff0c;注意目前可以使用中文名进行注册&#xff0c;最后证书上的名字也是中文名 考试前准备&#xff1a; 1、身份证 2、桌面除了电脑鼠标其他物品都收好 3、房间就自己一个人&#xff0c;不允许房间有其他人 4、网速要快&#xff0c;博主特意升级了自…

GBU814-ASEMI功率整流器件GBU814

编辑&#xff1a;ll GBU814-ASEMI功率整流器件GBU814 型号&#xff1a;GBU814 品牌&#xff1a;ASEMI 封装&#xff1a;GBU-4 恢复时间&#xff1a;&#xff1e;50ns 正向电流&#xff1a;8A 反向耐压&#xff1a;1400V 芯片个数&#xff1a;4 引脚数量&#xff1a;4 …

CGLIB代理,jsp,EL表达式,JSTL标准标签库

1、CGLIB代理 有一个类没有实现接口&#xff0c;想要对这个类实现增强&#xff0c;就需要使用CGLIB代理 导入CGLIB的包 <dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version> </depende…

集成易点易动管理系统连接更多应用

场景描述&#xff1a; 基于易点易动开放平台能力&#xff0c;无代码集成易点易动与多个应用互通互连&#xff0c;实现固定资产管理数字化、智能化。通过Aboter可搭建业务自动化流程&#xff0c;实现多个应用之间的数据连接。 开放能力&#xff1a; 消息推送&#xff1a; 新…

无涯教程-分类算法 - 多项式逻辑回归模型函数

Logistic逻辑回归的另一种有用形式是多项式Lo​​gistic回归&#xff0c;其中目标或因变量可以具有3种或更多可能的unordered类型&#xff0c;即没有定量意义的类型。 用Python实现 现在&#xff0c;无涯教程将在Python中实现上述多项式逻辑回归的概念。为此&#xff0c;使用…

前端js实现获取指定元素(top,lef,right,bottom)到视窗的距离 ;getBoundingClientRect()获取

getBoundingClientRect()获取元素位置&#xff0c;这个方法没有参数 该函数返回一个Object对象&#xff0c;该对象有6个属性&#xff1a;top,lef,right,bottom,width,height&#xff1b; <div id"box"></div>var objectdocument.getElementById(box); …

产教融合 | 中南大学暑期实训,用万应低代码践行敏捷开发之路

融合学究与实践&#xff0c;方能成为当代“数字英才”。 2023年8月11日&#xff0c;由潇湘大数据研究院、中南大学计算机学院及云畅科技联合组织的2020级数据科学与大数据技术专业暑期‘生产实训’项目圆满结束。本次实训全程线下进行&#xff0c;基于“深度创新培育计划”&…