洞概述
微软在4月份的例行补丁(4月12日)中对一个Office远程命令执行漏洞(CVE-2017-0199)进行了修补,但事实上在补丁发布之前已有多个利用此漏洞的攻击在野外被发现,其中包含分发银行恶意软件的案例。360天眼实验室也在之前得到相关的漏洞利用样本,分析确认为一个对中国做持续APT攻击的团伙的针对性攻击,这是与已知的其他安全厂商公布的不同来源的攻击,由此可见在此漏洞被修补之前已经在地下扩散到了非常大的范围。随着漏洞相关的技术细节的公开,由于漏洞影响大量的Office版本,利用此漏洞的攻击极有可能开始泛滥,需要引起高度重视。
该漏洞利用OFFICE OLE对象链接技术,将包裹的恶意链接对象嵌在文档中,OFFICE调用URL Moniker(COM对象)将恶意链接指向的HTA文件下载到本地, URL Moniker通过识别响应头中content-type的字段信息(图1)最后调用mshta.exe将下载到的HTA文件执行起来。
图1
利用此漏洞的通常攻击场景,用户收到一个包含恶意代码的Office文件(不限于RTF格式的Word文件,可能为PPT类的其他Office文档),点击尝试打开文件时会从外部网站下载特定的恶意HTA程序执行,从而使攻击者获取控制。
漏洞细节
这里基于Hash为5ebfd13250dd0408e3de594e419f9e01的样本文件对漏洞的利用细节做进一步的深入分析。
5ebfd13250dd0408e3de594e419f9e01是RTF格式的文件,内嵌的OLE对象类型被设置为OfficeDOC,嵌入形式是包裹进一个链接类型OLE对象,类型为ole2link(OLE对象中,其数据流偏移4的位置,如果为2则为包裹类型,如果是1的话则为链接类型)。链接形式的OLE对象本身不包含在文档本身中,而是位于文档之外,其中链接的对象可以在本机,也可以在远程服务器上,这是COM组件的一个特性,因为OLE本身就是COM组件的一部分。
下图为5ebfd13250dd0408e3de594e419f9e01中对象的结构信息
图2
URL Monkiler是COM对象,在RTF文件中,它的CLSID存放顺序与实际是部分颠倒的:
E0 C9 EA 79 F9 BA CE 11 8C82-00AA004BA90B (红色部分)。Office通过URL Moniker来发送远程请求,下载 http://46.102.152.129/template.doc ,MD5: 3c01c4d68ddcf1e4ecd1fa1ca3024c54,下载的文件是一个RTF文件,其中包含了VBS脚本(如图3)。之后URL Moniker通过content-type识别为HTA,最后调用mshta.exe加载。mstha.exe在匹配到脚本数据之后,执行其中包含的VBS(图4),可以看到这个VBS做了一些简单的混淆。
图3
图4
VBS脚本功能:
1.执行powershell命令结束winword.exe进程
2.下载http://hyoeyeep.ws/sp.exe文件,写入%appdata%\Microsoft\Windows\Start Menu\Programs\Startup\winword.exe,这样实现了自启动
3.下载http://hyoeyeep.ws/sp.doc写入%temp%\document.doc
4.清空注册表键值Word versions 15.0 and 16.0 Resiliency子键与键值。这样winword可以正常的启动
5.运行%appdata%\Microsoft\Windows\Start Menu\Programs\Startup\winword.exe。
6.调用winword打开document.doc。这是个正常文件,目的当然是造成正常处理文件的假象
sp.exe(a9e0d7e94f555dc3a2db5e59e92ed778)属于Dridex家族,是网银类的后门。这里就不详细分析了。下图是VirusTotal上的扫描结果:
图5
mshta执行template.doc的过程还值得提一下:
因为下载回来的template.doc文件格式是RTF,里面嵌入了vbscript,mshta会搜索文件数据,匹配可执行的脚本。mshta先会加载mshtml.dll并调用RunHtmlApplication这个导出函数,然后在CCHtmScriptParseCtx::Execute()中匹配脚本文件的标签,获取脚本对象,如图6。0x1fa2120为某数据对象,0x68C173A0处是该类对象的类函数,如图7。我们可以看到0x678128处是RTF文件的内容,0x4910为VBS脚本开始的偏移。经过匹配之后找到脚本数据,最后调用vbscirpt.dll执行脚本。
图6
图7
相关思考
COM/OLE技术是微软的一大技术亮点,但为开发人员提供了很大的便利同时,其组件的丰富特性也造成了许多的安全隐患,关于OLE所造成的安全漏洞可以参考《Attacking Interoperability: An OLE Edition》文档。对于CVE-2017-0199来说,其绕过了Office执行脚本的安全措施,OLE机制的3个特性组合下导致了这个漏洞:
1.OLE link object特性,本身提供了非常灵活的数据存储和操作架构。
2.URL Moniker特性,Office没有将请求对象类型与content-type做校验是导致HTA脚本执行的一个重要原因,远程请求文件根据content-type来运行对应程序,加载执行数据。
3.Windows在执行HTA文件的时候,会匹配搜索数据流,直到发现有脚本数据流。
这三个特性单个来看都不存在明显的安全隐患,都是为了尽可能实现正常功能,但是这些特性的组合造就了这个漏洞。正好应验了那句话,功能越强大组合方式越多存在安全问题的可能性就会越大。同时像CVE-2017-0199这种典型的利用Windows特性,实现攻击效果,对安全研究人员提出了新的考验,相信以后这种类型的机制组合漏洞还会出现。
参考资料
1.https://msdn.microsoft.com/en-us/library/ms775149(v=vs.85).aspx
2.https://www.fireeye.com/blog/threat-research/2017/04/cve-2017-0199-hta-handler.html
3.https://msdn.microsoft.com/en-us/library/dd942265.aspx
这个是c#类库方法根据注释生成帮助文档的工具,我们经常会遇到把DLL或者API提供给别人调用的情况,通过在方法中添加注释,然后再用Sandcastle 来自动生成文档给调用者,如下图:
图1:这是Sandcastle Help File Builder软件界面
图2:这是生成的chm文档
还可以直接给出示例代码:
图3:还可以直接生成网页
下载地址:
Help File Builder and Tools v2021.4.9.0最新版本
下载链接:https://github.com/EWSoftware/SHFB/releases
单纯Sandcastle好像是没有界面的, 这个链接提供的下载可以包含图形界面。
注意:如果需要生成chm还需要微软的 MicrosoftHTMLHelpWorkshop 支持,Sandcastle生成时会自动去查找MicrosoftHTMLHelpWorkshop 的安装目录。
安装:
安装很简单,两个软件都只需要直接点击“下一步”即可安装完成。
安装好软件后可以根据自己的需要配置相应的参数。
默认情况下dll中所有方法和属性都会生成对应文档,也可以根据自己需要只把DLL中需要的类或方法生成文档,可通过如下图配置:
在左侧把需要的类或方法勾选就行了:
在使用工具生成文档前,别忘了在VS中要作简单配置,才能生成DLL对应的XML配置文件,vs配置方法如下:
在VS中右键项目属性:
把"XML documentation file:"勾选,当编译时在生成DLL的同时还会生成一个和dll同名的xml配置文件。
在Sandcastle中右侧窗口右键将需要生成文档的dll和对应的xml添加进来:
点击工具栏上的
这个按钮就可以自动生成文档了。
为了生成友好的帮助文档,注释规范自然少不了,以下是关于C#的注释规范以及各参数的说明,注释越详细,生成的文档可读性越好:
1、C#注释标记:
大家对注释应该都不陌生,在方法或者类前面三个斜杠就自动添加了常用的注释标记,如下图:
但是如果想要得到更加友好的帮助文档,注释得花点心思。
如文章开头所展示的帮助文档,部分方法的注释如下:
2、C#注释标记说明:
A.2.1
此标记提供一种机制以指示用特殊字体(如用于代码块的字体)设置说明中的文本段落。对于实际代码行,请使用 (第 A.2.2 节)。
语法:text
示例:
/// Class Point models a point in a two-dimensional
/// plane.
public class Point
{
// ...
}
A.2.2
标记用于将一行或多行源代码或程序输出设置为某种特殊字体。对于叙述中较小的代码段,请使用 (第 A.2.1 节)。
语法:
source code or program output
示例:
/// This method changes the point's location by
/// the given x- and y-offsets.
/// For example:
///Point p = new Point(3,5);
/// p.Translate(-1,3);
/// results in p's having the value (2,8).
public void Translate(int xor, int yor) {
X += xor;
Y += yor;
}
A.2.3
此标记用于在注释中插入代码示例,以说明如何使用所关联的方法或其他库成员。通常,此标记是同标记 (第 A.2.2 节)一起使用的。
语法:description
示例:
有关示例,请参见 (第 A.2.2 节)。
A.2.4
此标记提供一种方法以说明关联的方法可能引发的异常。
语法:description
其中cref="member"
成员的名称。文档生成器检查给定成员是否存在,并将 member 转换为文档文件中的规范元素名称。description 对引发异常的情况的描述。
示例:
public class DataBaseOperations
{
public static void ReadRecord(int flag) {
if (flag == 1)
throw new MasterFileFormatCorruptException();
else if (flag == 2)
throw new MasterFileLockedOpenException();
// …
}
}
A.2.5
此标记允许包含来自源代码文件外部的 XML 文档的信息。外部文件必须是符合标准格式的 XML 文档,还可以将 XPath 表达式应用于该文档来指定应包含该 XML 文档中的哪些 XML 文本。然后用从外部文档中选定的 XML 来替换 标记。
语法:
filename" path="xpath" /
其中file="filename"
外部 XML 文件的文件名。该文件名是相对于包含 include 标记的文件进行解释的(确定其完整路径名)。
path="xpath"
XPath 表达式,用于选择外部 XML 文件中的某些 XML。
示例:
如果源代码包含了如下声明:
///"docs.xml" path='extradoc/class[@name="IntList"]/' /
public class IntList { … }
并且外部文件“docs.xml”含有以下内容:
"1.0"?
\ "IntList"
Contains a list of integers.
\ "StringList"
Contains a list of integers.
这样输出的文档就与源代码中包含以下内容时一样:
///
/// Contains a list of integers.
///
public class IntList { … }
A.2.6
此标记用于创建列表或项目表。它可以包含 块以定义表或定义列表的标头行。(定义表时,仅需要在标头中为 term 提供一个项。)
列表中的每一项都用一个 块来描述。创建定义列表时,必须同时指定 term 和 description。但是,对于表、项目符号列表或编号列表,仅需要指定 description。
语法:
term
description
term
description
…
term
description
其中
term
要定义的术语,其定义位于 description 中。
description
是项目符号列表或编号列表中的项,或者是 term 的定义。
示例:
public class MyClass
{
/// Here is an example of a bulleted list:
/// Item 1.
/// Item 2.
public static void Main () {
// ...
}
}
A.2.7.
此标记用于其他标记内,如 (第 A.2.11 节)或 (第 A.2.12 节),用于将结构添加到文本中。
语法:
content
其中
content
段落文本。
示例:
/// This is the entry point of the Point class testing program.
/// This program tests each method and operator, and
/// is intended to be run after any non-trvial maintenance has
/// been performed on the Point class.
public static void Main() {
// ...
}
A.2.8
该标记用于描述方法、构造函数或索引器的参数。
语法:description 其中name参数名。description参数的描述。
示例:
/// This method changes the point's location to
/// the given coordinates.
///the new x-coordinate.
///the new y-coordinate.
public void Move(int xor, int yor) {
X = xor;
Y = yor;
}
A.2.9
该标记表示某单词是一个参数。这样,生成文档文件后经适当处理,可以用某种独特的方法来格式化该参数。
语法:
name
其中
name
参数名。
示例:
/// This constructor initializes the new Point to
/// (,).
///the new Point's x-coordinate.
///the new Point's y-coordinate.
public Point(int xor, int yor) {
X = xor;
Y = yor;
}
A.2.10
该标记用于将成员的安全性和可访问性记入文档。
语法:
description
其中
cref="member"
成员的名称。文档生成器检查给定的代码元素是否存在,并将 member 转换为文档文件中的规范化元素名称。
description
对成员的访问属性的说明。
示例:
/// Everyone can
/// access this method.
public static void Test() {
// ...
}
A.2.11
该标记用于指定类型的概述信息。(使用 (第 A.2.15 节)描述类型的成员。)
语法
description
其中
description
摘要文本。
示例:
/// Class Point models a point in a
/// two-dimensional plane.
public class Point
{
// ...
}
A.2.12
该标记用于描述方法的返回值。
语法:
description
其中
description
返回值的说明。
示例:
/// Report a point's location as a string.
/// A string representing a point's location, in the form (x,y),
/// without any leading, trailing, or embedded whitespace.
public override string ToString() {
return "(" + X + "," + Y + ")";
}
A.2.13
该标记用于在文本内指定链接。使用 (第 A.2.14 节)指示将在“请参见”部分中出现的 文本。
语法:
member"/
其中
cref="member"
成员的名称。文档生成器检查给定的代码元素是否存在,并将 member 更改为所生成的文档文件中的元素名称。
示例:
/// This method changes the point's location to
/// the given coordinates.
///
public void Move(int xor, int yor) {
X = xor;
Y = yor;
}
/// This method changes the point's location by
/// the given x- and y-offsets.
///
///
public void Translate(int xor, int yor) {
X += xor;
Y += yor;
}
A.2.14
该标记用于生成将列入“请参见”部分的项。使用 (第 A.2.13 节)指定来自文本内的链接。
语法:
member"/
其中
cref="member"
成员的名称。文档生成器检查给定的代码元素是否存在,并将 member 更改为所生成的文档文件中的元素名称。
示例:
/// This method determines whether two Points have the same
/// location.
///
///
public override bool Equals(object o) {
// ...
}
A.2.15
可以用此标记描述类型的成员。使用 (第 A.2.11 节)描述类型本身。
语法:
description
其中
description
关于成员的摘要描述。
示例:
/// This constructor initializes the new Point to (0,0).
public Point() : this(0,0) {
}
A.2.16
该标记用于描述属性。
语法:
property description
其中
property description
属性的说明。
示例:
/// Property X represents the point's x-coordinate.
public int X
{
get { return x; }
set { x = value; }
}
A.3. 处理文档文件
文档生成器为源代码中每个附加了“文档注释标记”的代码元素生成一个 ID 字符串。该 ID 字符串唯一地标识源元素。文档查看器利用此 ID 字符串来标识该文档所描述的对应的元数据/反射项。
文档文件不是源代码的层次化表现形式;而是为每个元素生成的 ID 字符串的一维列表。
A.3.1. ID 字符串格式
文档生成器在生成 ID 字符串时遵循下列规则:
不在字符串中放置空白。
字符串的第一部分通过单个字符后跟一个冒号来标识被标识成员的种类。定义以下几种成员:
字符串的第二部分是元素的完全限定名,从命名空间的根开始。元素的名称、包含着它的类型和命名空间都以句点分隔。如果项名本身含有句点,则将用 # (U+0023) 字符替换。(这里假定所有元素名中都没有“# (U+0023)”字符。)
对于带有参数的方法和属性,接着是用括号括起来的参数列表。对于那些不带参数的方法和属性,则省略括号。多个参数以逗号分隔。每个参数的编码都与 CLI 签名相同,如下所示:参数由其完全限定名来表示。例如,int 变成 System.Int32、string 变成 System.String、object 变成 System.Object 等。具有 out 或 ref 修饰符的参数在其类型名后跟有 @ 符。对于由值传递或通过 params 传递的参数没有特殊表示法。数组参数表示为 [ lowerbound : size , … , lowerbound : size ],其中逗号数量等于秩减去一,而下限和每个维的大小(如果已知)用十进制数表示。如果未指定下限或大小,它将被省略。如果省略了某个特定维的下限及大小,则“:”也将被省略。交错数组由每个级别一个“[]”来表示。指针类型为非 void 的参数用类型名后面跟一个 *的形式来表示。void 指针用类型名 System.Void 表示。
A.3.2. ID 字符串示例
下列各个示例分别演示一段 C# 代码以及为每个可以含有文档注释的源元素生成的 ID 字符串:
类型用它们的完全限定名来表示。
enum Color { Red, Blue, Green }
namespace Acme
{
interface IProcess {...}
struct ValueType {...}
class Widget: IProcess
{
public class NestedClass {...}
public interface IMenuItem {...}
public delegate void Del(int i);
public enum Direction { North, South, East, West }
}
}
"T:Color"
"T:Acme.IProcess"
"T:Acme.ValueType"
"T:Acme.Widget"
"T:Acme.Widget.NestedClass"
"T:Acme.Widget.IMenuItem"
"T:Acme.Widget.Del"
"T:Acme.Widget.Direction"
字段用它们的完全限定名来表示。
namespace Acme
{
struct ValueType
{
private int total;
}
class Widget: IProcess
{
public class NestedClass
{
private int value;
}
private string message;
private static Color defaultColor;
private const double PI = 3.14159;
protected readonly double monthlyAverage;
private long[] array1;
private Widget[,] array2;
private unsafe int *pCount;
private unsafe float **ppValues;
}
}
"F:Acme.ValueType.total"
"F:Acme.Widget.NestedClass.value"
"F:Acme.Widget.message"
"F:Acme.Widget.defaultColor"
"F:Acme.Widget.PI"
"F:Acme.Widget.monthlyAverage"
"F:Acme.Widget.array1"
"F:Acme.Widget.array2"
"F:Acme.Widget.pCount"
"F:Acme.Widget.ppValues"
构造函数。
namespace Acme
{
class Widget: IProcess
{
static Widget() {...}
public Widget() {...}
public Widget(string s) {...}
}
}
"M:Acme.Widget.#cctor"
"M:Acme.Widget.#ctor"
"M:Acme.Widget.#ctor(System.String)"
析构函数。
namespace Acme
{
class Widget: IProcess
{
~Widget() {...}
}
}
"M:Acme.Widget.Finalize"
方法。
namespace Acme
{
struct ValueType
{
public void M(int i) {...}
}
class Widget: IProcess
{
public class NestedClass
{
public void M(int i) {...}
}
public static void M0() {...}
public void M1(char c, out float f, ref ValueType v) {...}
public void M2(short[] x1, int[,] x2, long[][] x3) {...}
public void M3(long[][] x3, Widget[][,,] x4) {...}
public unsafe void M4(char *pc, Color **pf) {...}
public unsafe void M5(void *pv, double *[][,] pd) {...}
public void M6(int i, params object[] args) {...}
}
}
"M:Acme.ValueType.M(System.Int32)" "M:Acme.Widget.NestedClass.M(System.Int32)"
"M:Acme.Widget.M0" "M:Acme.Widget.M1(System.Char,System.Single@,Acme.ValueType@)"
"M:Acme.Widget.M2(System.Int16[],System.Int32[0:,0:],System.Int64[][])"
"M:Acme.Widget.M3(System.Int64[][],Acme.Widget[0:,0:,0:][])"
"M:Acme.Widget.M4(System.Char*,Color**)"
"M:Acme.Widget.M5(System.Void*,System.Double*[0:,0:][])"
"M:Acme.Widget.M6(System.Int32,System.Object[])"
属性和索引器。
namespace Acme
{
class Widget: IProcess
{
public int Width { get {...} set {...} }
public int this[int i] { get {...} set {...} }
public int this[string s, int i] { get {...} set {...} }
}
}
"P:Acme.Widget.Width" "P:Acme.Widget.Item(System.Int32)" "P:Acme.Widget.Item(System.String,System.Int32)"
事件。
namespace Acme
{
class Widget: IProcess
{
public event Del AnEvent;
}
}
"E:Acme.Widget.AnEvent"
一元运算符。
namespace Acme
{
class Widget: IProcess
{
public static Widget operator+(Widget x) {...}
}
}
"M:Acme.Widget.op_UnaryPlus(Acme.Widget)"
下面列出可使用的一元运算符函数名称的完整集合:op_UnaryPlus、op_UnaryNegation、op_LogicalNot、op_OnesComplement、op_Increment、op_Decrement、op_True 和 op_False。
二元运算符。
namespace Acme
{
class Widget: IProcess
{
public static Widget operator+(Widget x1, Widget x2) {...}
}
}
"M:Acme.Widget.op_Addition(Acme.Widget,Acme.Widget)"
下面列出可使用的二元运算符函数名称的完整集合:op_Addition、op_Subtraction、op_Multiply、op_Division、op_Modulus、op_BitwiseAnd、op_BitwiseOr、op_ExclusiveOr、op_LeftShift、op_RightShift、op_Equality、op_Inequality、op_LessThan、op_LessThanOrEqual、op_GreaterThan 和 op_GreaterThanOrEqual。
转换运算符具有一个尾随“~”,然后再跟返回类型。
namespace Acme
{
class Widget: IProcess
{
public static explicit operator int(Widget x) {...}
public static implicit operator long(Widget x) {...}
}
}
"M:Acme.Widget.op_Explicit(Acme.Widget)~System.Int32"
"M:Acme.Widget.op_Implicit(Acme.Widget)~System.Int64"
好了,以上就是关于Sandcastle的使用,相信大家以后都可以用得上,同时也给自己留作备忘。
为了节省大家的时间,这一套软件都放到某度网盘了。给本公号发送【文档工具】可以获取链接。
转自:Alen Liu
链接:cnblogs.com/Alenliu/p/15140751.html
新的漏洞利用开始渐渐脱离基于ROP的代码重用攻击。在过去的两年里,出现了一些关于一种新的代码重用攻击的文章,Counterfeit Object-Oriented Programming(COOP)。COOP是一种顶级的针对forward-edge的执行流完整性(CFI) 的攻击方式。在我们把CFI解决方案(HA-FI)整合进我们的终端产品中时,这种攻击吸引了我们的注意力。COOP主要出现在学术界,还没有出现在exloit工具包里。这也许是因为攻击者更趋向于使用更简单的方法。在win10年度更新中微软的Edge使用了执行流保护(CFG,Control Flow Guard)。在CFG中,缺少backward-edge的CFI更容易受到攻击。但是当Return Flow Guard (RFG)出现,使得攻击者不能再依靠淹没栈中的返回地址进行攻击的时候,会发生些什么呢?
我们对评估COOP在攻击CFI时的效果很感兴趣。这不仅可以使我们保持在学术界和黑客社区中前沿研究中的地位,也可以测试产品的有效性,更改设计,甚至在必要时普遍地提高我们自己的防御能力。在我们的这一系列的两篇博文中的第一篇中,介绍了我们使用COOP函数重用对微软的CFG以及我们自己的HA-CFI进行攻击的评测。
微软执行流保护
已经有大量的论文,博文和会议发言充分地讨论了微软的执行流保护(CFG,Microsoft’s Control Flow Guard)。Trail of Bits在两篇最近的帖子里比较了Clang CFI和微软CFG。第一篇帖子着重Clang,第二篇强调微软对CFI的实现,还有额外的研究提供了CFG的实现的进一步细节。
在过去的几年里,绕过CFG也成为了安全会议中中一个流行的主题。在我们引用一些著名的绕过方法之前,最重要的是CFI能够进一步分为两种:forward-edge和backward-edge。
Forward-Edge CFI: 保护间接调用或是JMP位置. Forward-edge CFI解决方案包括微软 CFG和Endgame的HA-CFI.
Backward-Edge CFI: 保护返回指令. Backward-edge CFI解决方案包括微软的Return Flow Guard,Endgame的DBI exploit防护的一部分, 以及包括intel的CET在内的其他ROP检测。
这个分类帮助我们描绘出了CFG保护位置的轮廓——间接调用位置——以及不打算保护的位置——栈返回地址。例如,一个最近的POC入选了exploit工具包,这个POC针对Edge,使用读/写的原始方法来修改栈中的返回地址。但这并不适用于CFG,不应该作为CFG的弱点来考虑。尽管如此,它成功地证明了CFG的有效性,并使攻击者转向劫持执行流,而不是间接调用的位置。这个例子实际上证明了CFG缺陷包括以下几点:利用未受保护的函数调用位置,重映射包括CFG代码在内的只读内存区域,并使他们指向需要受到检查的代码,在Charka中提到的JIT编码器的资源竞争,使用基于内存的间接调用。COOP或是函数重用攻击,在面对CFI的实现时有着公认的局限,因为“limitations of coarse-grained CFI”,他们并没有入选微软的bypass赏金。也就是说,我们不知道有哪些公有领域的POCs能证明COOP能指定攻击CFG的加固的二进制代码。
CFG对每个受保护的DLL添加了一个__guard_fids_table,它由一系列在二进制代码中合法的RVAs或是间接调用指令中敏感的目的地址组成。一个地址作为CFG bitmap索引的一部分而存在。bitmap里的bits能够根据地址是否是合法的目的地址而进行切换。在此之外,也有一个API能够对bitmap进行修改,例如,为了支持JIT编码的页面:
kernelbase!SetProcessValidCallTargets在使用系统调用更新bitmap之前会调用ntdll!SetInformationVirtualMemory
win10创意者更新有一项新增的功能可以抑制导出,也就是说,现在导出函数能在CFG保护的调用位置被标记为非法目的地址。这一功能的实现需要使用CFG Bitmap中每一个地址的第二位,以及在初始化每个进程的bitmap时__guard_fids_table中每一个RVA条目的一个标记字节。
对于64位的系统,地址的第9-63位被用于在CFGbimap中检索一个qword,第3-10位被用于(模64)访问qword中某一指定位。在导出被抑制后,CFG允许一个给定的地址在CFG bitmap中用两位表示。此外,在大多数DLLs中__guard_dispatch_icall_fptr现在被设置为指向ntdll!LdrpDispatchUserCallTargetES,在其中一个合法的调用目标必须从CFG bitmap中删去。
当你把动态解析符号表考虑进去的时候,实现这样一个导出表抑制变得有点复杂,因为使用GetProcAddress意味着随后的代码也能调用返回值作为函数指针。只要CFG bitmap中每一个条目没有被标记为敏感的或是不合法的(例如,VirtualProtect, SetProcessValidCallTargets等等),执行流保护可以通过把条目对应的两位从“10”(导出表抑制)改为“01”(合法的调用位置),解决这个问题。最后,一些导出表将会在进行创建时以不合法的间接调用开始,但最终在运行时代码中成为合法的调用目的地址。在今后我们的讨论中,这尤为重要。当这一情况发生时,一个调用栈的样例如下:
00 nt!NtSetInformationVirtualMemory
01 nt!setjmpex
02 ntdll!NtSetInformationVirtualMemory
03 ntdll!RtlpGuardGrantSuppressedCallAccess
04 ntdll!RtlGuardGrantSuppressedCallAccess
05 ntdll!LdrGetProcedureAddressForCaller
06 KERNELBASE!GetProcAddress
07 USER32!InitializeImmEntryTable
COOP概要
Schuster et al.认为COOP是CFI实现的一个潜在的弱点。为了在绕过forward-edge CFI的检查之后执行代码,我们可以利用连续的攻击序列和重用已存在的虚函数。在ROP在有一个相似的方法,其结果是一系列小段合法函数,每一段代码实现最低限度的功能(例如,载入一个值进RDX中),但把它们组合在一起,却可以实现一些复杂的任务。COOP的一个基本组成部分就是利用主循环函数,在其中可以迭代对象链表或数组,调用每个对象中的虚函数。然后,攻击者把内存中“伪装”的对象组合起来,在某些情况下,可能会覆盖对象,这样就能在主循环中按攻击者安排好的顺序调用合法的虚函数。Schuster et al.证明了使用COOP payloads的攻击win7 32位和64位上的IE10,以及Linux 64位上的Firefox的方法。这项研究随后被扩展了,证明了递归或是带有许多非直接调用的函数也可以实现这一过程,而不仅仅是循环。随后又继续被扩展到用于攻击Objective-C 运行时环境。
这项前沿研究极其有趣和新奇。我们想要把这一概念应用到一些现代的CFI实现上,以对如下方案进行评估:a)在加固的浏览器中构造一个COOP payload的难度;b)是否能绕过CFG和HA-CFI;c)是否能改进CFI使其能检测到COOP类型的攻击。
我们的目标
我们使用COOP主要的目标是win10的Edge,因为它代表着一个全新的CFG加固应用,并且它能让我们在内存中使用JavaScript来准备我们的COOP payload。弱点始终是我们小组的兴趣,为了这个目标,我们专注于劫持CFI的执行流,并对攻击者作出了下列假设:
1.任意的读-写原语都是从JavaScript中获得的。
2.因为在运行时动态地找到小段代码不是这项研究的内容,因此,允许使用硬编码偏移量。
3.所有微软创意者更新中最近的防御机制都能被使用(例如,ACG,CIG,带导出表抑制的CFG)。
4.除了使用COOP以外,攻击者不允许以任何方式绕过CFG。
在我们最初的研究里,我们在对微软年度更新(OS build 14393.953)中的Edge的研究中利用了一个Theori中的POC,我们使用创意者更新中的防御机制设计我们的payload,并在开启导出表抑制的win10创意者更新(OS build 15063.138)中对其进行验证。
一个理想的POC会执行一些攻击者的shellcode或是启动一个应用程序。攻击者的一个经典的代码执行模型,就是把一些内存中被控制的数据映射为+X,然后跳转到包含最新修改过的+X区域的shellcode。然后,我们的真实目的是在forward-edge CFI的保护下,产生一个能够执行一些有意义的代码的COOP payloads。这样一个payload提供了能够进行测试和改善我们的CFI算法的数据。进一步说,攻击Arbitrary Code Guard (ACG)或是Edge的子进程的办法超出了我们的研究范围。我们确定对于win10创意者更新研究的最终目标是使用COOP来使CFG无效,使得在DLL内能够跳转或是调用任意位置的代码。因此,我们总结出下面两个主要的COOP payloads:
1.对于win10年度更新,以及缺少ACG保护的程序,我们的payload把我们的控制的数据映射为可执行的代码,在使得CFG无效后跳转到我们控制的shellcode所在区域。
2.对于win10创意者更新,我们的最终目标是仅仅是使CFG无效。
寻找COOP片段
下列Schuster et al.设想的蓝图,我们的第一业务是商定COOP各个组成部分的术语。学术论文将每个重用函数称为虚函数片段(virtual function gadget)或是vfgadget,当我们描述每一个特定类型的vfgadget时使用缩写,例如将主循环(main loop)vfgadget称为ML-G。我们选择以更为非正式的方式来命令每种类型的gadget。在接下来的帖子中你能找到的术语定义如下:
Looper:对于执行复杂COOPpayloads(论文中的ML-G)至关重要的主循环gadget。
Invoker:一个调用vfgadget的函数指针。(论文中的INV-G)
Arg Populator:带一个参数的虚拟函数,它将一个值加载到寄存器中(论文中的LOAD-R64-G),或是移动栈指针或是把值加载进栈中(论文中的MOVE-SP-G)
与论文相似,我们编写了脚本来帮助我们识别二进制中的vfgadgets。我们使用了IPA Python,推理帮助我们找到了loopers,invokers和argument pupulators。在我们的研究中,我们发现了实现COOP的实用的方法就是,在返回到JavaScript之前,把vfgadgets链接到一起并依次执行少量的vfgadgets。根据需要通过额外的COOP payloads重复这个过程。因此,为了我们的目的,我们发现没有必要将二进制代码提升到IR。然而,将大量COOP payload拼接到一起,比如说完全通过重用代码运行一个C2 socket线程,也许会需要提升到IR。对于vfgadget的每个子类型,我们定义了一系列规则,并使用它在Edge(chakra.dll和edgehtml.dll)的两个二进制文件间进行搜索。这些规则中与looper vfgadget相关的一部分包括:
1.出现在__guard_fids_table中的函数
2.包含一个不带参数的间接调用的循环
3.循环不能影响到参数寄存器
在vfgadgets的所有类中,搜索loopers是最耗时的。许多潜在的loopers有一些限制使其难以使用。我们寻找到的invokers不仅需要有调用虚函数指针的vfgadgets,还要能够在单一的counterfeit对象中,一次性又快又容易地填充六个参数的vfgadgets。因此,当尝试调用单个API时,COOP可以使用快捷方式,完全避免对循环和递归的需求,除非需要返回值。在x64程序上能够找到许多寄存器对参数寄存器进行填充。值得一提的是,Schuster et al.的COOP论文中根据mshtml提出的大量原始vfgadgets仍然能在edgehtml中找到。然而,我们在我们的成果中添加了一个要求来避免重用这些,而不是为我们的COOP payloads寻找新的vfgadgets。
COOP Payloads
通过脚本语言触发COOP,我们实际上能把一些复杂的任务从COOP中移开,因为一次性把所有东西拼接在一起非常的复杂。我们能使用JavaScript来帮助我们,重复调用微型COOP payload序列。这也让我们能把诸如算术和条件操作放回JavaScript中执行,并保留基本的函数重用来为通过COOP调用重要的API做准备。此外,我们展示了这种方法的一个例子,包括在我们劫持到的#1 section中将COOP的返回值传回到JavaScript,并讨论如何调用LoadLibrary。
为了简洁,我将只介绍最简单的payloads。payloads的一个公共的主题是需要调用VirtualProtect。因为VirtualProtect和eshims(译者注:应该是ieshims)APIs被标记为敏感的且在CFG中并不是一个合法的目的地址,我们不得不在创意者更新中使用包装函数。正如Thomas Garnier所建议的那样,可以在.net库mscoree.dll和mscories.dll中方便地找到包装函数,例如UtilExecutionEngine::ClrVirtualProtect。因为微软的ACG可以防止创建新的可执行内存,以及把已有可执行内存改为可写,因此,我们需要一个替代方法。使用VitualProtect可以把只读内存重映射为可写的,所以我借用了2015年黑帽大会里介绍的这种技术,并将包含chakra! __guard_dispatch_icall_fptr的页面重新映射为可写,然后重写函数指针,使其指向包含jmp rax指令的chakra.dll中的任意位置。事实上,在大多数DLL中已经存在一个函数__guard_dispatch_icall_nop,它刚好就是一个单一的jmp rax指令。因此,我就能有效地绕过CFG的保护,因为在通过了所有检查之后,在chakra.dll中所有被保护的调用位置将立即跳转到目的地址。想必我们可以采用这种方法进一步探索使用函数重用攻击ACG的方法。为了完成这个小小的链接过程,需要以下满足以下条件:
1.把mscoroc.dll载入进Edge进程
2.在chakra.dll的只读内存区域调用ClrVirtualProtect +W
3.重写__guard_dispatch_icall_fptr以通过检查
从上面的vfgadgets列表可以看出,对于COOP来说edgehtml是一个重要的库。因此,我们的第一任务就是泄漏edgehtml的基址以及其他必要的组件,例如我们的counterfeit内存区域。这样,payload就能包含硬编码的偏移并在运行时重新定位。使用Theori的POC中泄漏的bug,我们就能获得我们想要的基地址。
//OS Build 10.0.14393
var chakraBase = Read64(vtable).sub(0x274C40);
var guard_disp_icall_nop = chakraBase.add(0x273510);
var chakraCFG = chakraBase.add(0x5E2B78); //_guard_dispatch_icall...
var ntdllBase = Read64(chakraCFG).sub(0x95260);
//Find global CDocument object, VTable, and calculate EdgeHtmlBase
var [hi, lo] = PutDataAndGetAddr(document);
CDocPtr = Read64(newLong(lo + 0x30, hi, true));
EdgeHtmlBase = Read64(CDocPtr).sub(0xE80740);
//Rebase our COOP payload
rebaseOffsets(EdgeHtmlBase, chakraBase, ntdllBase, pRebasedCOOP);
触发COOP
使用COOP的一个关键部分就是在最初把JavaScript传递进looper中。使用我们假设的R/W原语,我们可以轻易地劫持到chakra的vtable,使其指向我们的looper,但我们怎么确保looper会开始迭代我们counterfeit的数据呢?对于这个答案,我们需要进looper进行评估,在这里我使用了CTravelLog::UpdateScreenshotStream:
注意在循环前的第一个块中,代码是在+0x30处获取到链表的指针。为了正确启动looper,我们需要劫持JavaScript对象的vtable,使其地址包含在我们的looper 中,然后在对象+0x30处放置一个指针使其指向counterfeit对象列表的首部。实际的counterfeit对象数据可以通过JavaScript进行定义和重新定位。还要注意,循环在对象+0x80h处的的下一个指针列表处进行迭代。当构造counterfeit流时这很重要。此外,请注意,这个间接调用的位置在vtable+0xF8h处。在counterfeit对象中的任意伪vtable都必须指向设计好的函数指针减0xF8h处,这个地址通常是在邻接vtable表的中间。为了启动COOP的payload,我劫持了JavascriptNativeIntArray对象,并地freeze()和seal()虚函数进行了重载,如下所示:
本文由 看雪翻译小组 梦野间 编译,来源 Matt Spisak@Endgame
*请认真填写需求信息,我们会在24小时内与您取得联系。