shad0w原理分析 part 1
01 生成木马的方式
由作者的介绍可以得知,shad0w生成木马的命令为
python3 shad0w.py beacon -p x64/windows/secure -H 192.168.1.81 -f exe -o beacon.exe
payload 分为两种:
- 1、x64/windows/secure 分步加载
- 2、x64/windows/secure/static 直接生成beacon
secure 分步加载
get_payload_variables函数是对传入的payload参数进行提取,提取出4个变量
1. self.arch 位数(x64,x86)
2. self.platform 环境windows
3. self.secure 是否启用保护
4. self.static 是否直接生成beacon
由上面提取的static变量判断是否分步执行,如果static为null,则将stager目录下的所有文件拷贝到build里面。
并更新配置信息,更新的主要为
_C2_CALLBACK_ADDRESS 回连ip
_C2_CALLBACK_PORT 回连端口
下一步为编译build里面的内容,
1、如果使用了secure,则会在makelist中添加宏变量secure(此变量用处为beacon中启用syscall)
2、_crypt_strings 会异或加密函数名并将加密后字符串和key写入到strings.h
3、使用make编译stager
最后,payload_format.create函数中将stager变成shellcode
根据输出结果对输出内容进行判断,如果为raw直接生成shellcode,exe 是先生成shellcode,再将shellcode写入一个加载器编译成exe,dll也是一样。
shellcode生成是使用donut这个项目
static 生成beacon
静态是将src或者injectable目录的代码拷贝到build编译生成,后面步骤和上面一样。(经测试src生成的beacon功能存在bug,此处我直接使用injectable中代码生成beacon)
02 stager和beacon
stager
stager代码再beacon/stager下
stager功能代码较为简单
第一步、向服务器请求beacon
简单说是使用winhttp库发起https请求
向/stager发起post请求,参数是payload=x64/windows/secure/static(定义了secure)或payload=x64/windows/static(没有secure)(x64,x86是根据系统位数判断)
beacon会在shad0w启动listen时生成
过程和第一步生成stager的时候一样,不过生成的位置是在/tmp/beacon_id/build下面。生成的exe转成shellcode进行base64编码会存入shad0w.payloads"x64_secure_static"。
服务端是使用flask启动的服务器,会在控制端请求/stager返回shad0w.payloads["x64_secure_static"]["bin"]的内容。
第二步、使用shellcode加载器加载beacon
beacon
第一步、收集用户信息、 NetBIOS
首先获取sid,后通过sid获取username、domain
获取机器的位数、系统版本、是否编译时定义secure
第二步向服务端注册信息
向/register 发送上面收集的信息(username、domain、netbios、位数、系统版本、是否开启secure)
会将服务端返回的id记录在IdBuffer中。
服务端会记录用户信息,机器信息并生成beacon_id,返回beacon_id 第三步 会根据sleep休眠时间定期向服务端发送心跳包。
心跳包发送的方式为
向/tasks发起请求,参数为之前获取的beacon_id
解析返回结果。task用于后续用于判断执行方式。
InjectExecuteCode:注入shellcode于一个运行的进程。
SpawnExecuteCode: 新启一个进程注入shellcode
Stdlib:自带库。
InjectExecuteDll:反射型dll注入注入dll。
args为后续操作执行的内容。
服务端处理
在请求中获取beacon_id。opcode, data是在后续执行命令时获取,心跳包控制端只传递beacon_id。
opcode为0, data为空 即心跳包传递。
返回存活状态和task(0x1000)。(在beacon中0x1000为跳出)
03 免杀方式syscall介绍
shad0w使用的syscall的方式并不是硬编码,而是在程序执行开始时读取ntdll并将其写入到内存,从而实现避免被edr/av hook。
先看一个函数使用syscall执行的流程。
ParseNtdll(&NtdllInfo, &rSyscall);
MakeSyscall("NtOpenThread", NtdllInfo.pExprtDir, NtdllInfo.lpRawData, NtdllInfo.pTextSection, NtdllInfo.pRdataSection, SyscallStub);
rSyscall.NtOpenThread(&hThread, PROCESS_ALL_ACCESS, &ObjectAttributes, &uTid);
CleanSyscall(SyscallStub);
说一下syscall执行的流程
1. 在程序执行开始时将ntdll.dll写入内存
2. 解析ntdll的.text,.rdata节:
.text里面存储导出函数执行的代码 .rdata里面存储的是导出函数的名称
3. 在内存中找到要调用的函数(比如:NtOpenThread),将其拷贝到内存其他位置
4. 声明函数原型
5. 定义函数类型的变量指向内存,通过变量调用系统调用。
04 执行方式介绍
1.InjectExecuteCode
- 首先通过进程的pid打开进程。
- 将内存属性更改为可读可写,将shellcode拷贝到内存。
- 改为内存属性为可读可写可执行。
- 获取当前线程的句柄,挂起线程。
- 获取目标线程的上下文
- 设置rip(程序指令寄存器)的地址为shellcode的地址
- 恢复线程
- shellcode执行
2.SpawnExecuteCode
- 创建匿名管道,创建线程从匿名管道读取结果,向服务端发送结果。
2.创建进程,在进程中分配内存,写入shellcode,apc注入执行
3.Stdlib
这个是控制端自写的一些功能,主要是从服务端接受命令并进入相应的函数去执行。这个在将命令模块的时候再具体介绍。
4.InjectExecuteDll
1、这个函数主要是通过pe文件格式解析导出表,找到ReflectiveLoader函数的入口点地址。2、通过前面InjectExecuteCode方法的挂起线程,将程序寄存器(rip)地址改成ReflectiveLoader函数的地址。3、ReflectiveLoader会恢复dll在内存中的位置从而执行。
未完待续
对shad0w的命令模块还没有进行介绍。
1. 命令模块存在一些bug
2. 大量的模块都是通过将exe转成shellcode,再通过SpawnExecuteCode的方式执行,需要将一些常用的模块二次开发后再进行介绍。
3. 目前已经重写upload,download模块。
4. 后续添加shell执行命令的模块,将shellcode加载部分更改加载方式,不使用在其他进程中加载。
