洞概述
微軟在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小時內與您取得聯系。