惡意軟體開發——記憶體相關API

Carlison丶發表於2021-08-28

一、前言

Windows作業系統的記憶體有三種屬性,分別為:可讀、可寫、可執行,並且作業系統將每個程式的記憶體都隔離開來,當程式執行時,建立一個虛擬的記憶體空間,系統的記憶體管理器將虛擬記憶體空間對映到實體記憶體上,所以每個程式的記憶體都是等大的。

作業系統給予每個程式申請記憶體的權利,使用不同的API,申請的記憶體具有不同的涵義。

在程式申請時,需要宣告這塊記憶體的基本資訊:申請記憶體大小、申請記憶體起始記憶體基址、申請記憶體屬性、申請記憶體對外的許可權等。

二、相關API介紹

1.VirtualAlloc
該函式的功能是在呼叫程式的虛地址空間,預定或者提交一部分頁,如果用於記憶體分配的話,並且分配型別未指定MEM_RESET,則系統將自動設定為0;其函式原型:

LPVOID VirtualAlloc{
  LPVOID lpAddress, // 要分配的記憶體區域的地址
  DWORD dwSize, // 分配的大小
  DWORD flAllocationType, // 分配的型別
  DWORD flProtect // 該記憶體的初始保護屬性
};

引數說明:
1.LPVOID lpAddress, 分配記憶體區域的地址。當你使用VirtualAlloc來提交一塊以前保留的記憶體塊的時候,lpAddress引數可以用來識別以前保留的記憶體塊。如果這個引數是NULL,系統將會決定分配記憶體區域的位置,並且按64-KB向上取整(roundup)。
2.SIZE_T dwSize, 要分配或者保留的區域的大小。這個引數以位元組為單位,而不是頁,系統會根據這個大小一直分配到下頁的邊界DWORD
3.flAllocationType, 分配型別 ,你可以指定或者合併以下標誌:MEM_COMMIT,MEM_RESERVE和MEM_TOP_DOWN。
4.DWORD flProtect 指定了被分配區域的訪問保護方式:PAGE_EXECUTE_READ,PAGE_EXECUTE_READWRITE
小結:
VirtualAlloc可以通過並行多次呼叫提交一個區域的部分或全部來保留一個大的記憶體區域。多重呼叫提交同一塊區域不會引起失敗。這使得一個應用程 序保留記憶體後可以隨意提交將被寫的頁。當這種方式不在有效的時候,它會釋放應用程式通過檢測被保留頁的狀態看它是否在提交呼叫之前已經被提交。
VirtualAlloc對應的釋放函式為VirtualFree。

2.HeapAlloc
該函式是從堆上分配一塊記憶體,且分配的記憶體是不可移動的(即如果沒有連續的空間能滿足分配的大小,程式不能將其他零散的空間利用起來,從而導致分配失敗)。該分配方法是從一指定地址開始分配,而不像GloabalAlloc是從全域性堆上分配,這個有可能是全域性,也有可能是區域性,其函式原型:

LPVOID HeapAlloc(
  HANDLE hHeap, //要分配堆的控制程式碼
  DWORD dwFlags, //堆分配時的可選引數
  SIZE_T dwBytes, //要分配堆的位元組數
);

引數說明:
1.HANDLE hHeap,要分配堆的控制程式碼,可以通過HeapCreate()函式或GetProcessHeap()函式獲得。
2.DWORD dwFlags,堆分配時的可選引數,其值可以為以下的一種或多種:HEAP_GENERATE_EXCEPTIONS(如果分配錯誤將會丟擲異常,而不是返回NULL。異常值可能是STATUS_NO_MEMORY, 表示獲得的記憶體容量不足,或是STATUS_ACCESS_VIOLATION,表示存取不合法),HEAP_NO_SERIALIZE(不使用連續存取),HEAP_ZERO_MEMORY(將分配的記憶體全部清零)。
3.SIZE_T dwBytes,要分配堆的位元組數。
小結:
hHeap是程式堆記憶體開始位置,dwFlags是分配堆記憶體的標誌。包括HEAP_ZERO_MEMORY,即使分配的空間清零,dwBytes是分配堆記憶體的大小,其對應的釋放空間函式為HeapFree。

3.GlobalAlloc
該函式用於從全域性堆中分配出記憶體供程式使用,函式原型為:

HGLOBALGlobalAlloc(
  UINTuFlags, // 分配屬性(方式)
  DWORDdwBytes, // 分配的位元組數
);

引數說明:
1.UINTuFlags,指定如何分配記憶體,若指定為0,則是預設的GMEM_FIXED.這個值可以是下面其中一個或幾個位標識(那些指明不相容的組合除外),標識:GHND(為GMEM_MOVEABLE 和 GMEM_ZEROINIT的組合),GMEM_FIXED(分配固定的記憶體,返回值是一個指標),GMEM_MOVEABLE(分配可移動的記憶體,在Win32中記憶體塊在實體記憶體中是不可移動的,但在預設堆中可以. 返回值是該記憶體物件的控制程式碼,可使用函式 GlobalLock 將該控制程式碼轉換為一個指標,這個標識不能與 GMEM_FIXED 組合使用),GMEM_ZEROINIT(將所申請記憶體初始化為0),GPTR(為GMEM_FIXED和GMEM_ZEROINIT組合)。
2.DWORDdwBytes,指定要申請的位元組數.若該引數為 0 且引數 uFlags 指定為 GMEM_MOVEABLE 則該函式返回一個記憶體物件的控制程式碼,該記憶體物件被標識為discarded(可拋棄的)
小結:
使用此函式分配記憶體可以保證8位元組的邊界.所有的記憶體均在執行訪問時建立;不需要特別的函式來動態執行所產生的程式碼,若函式呼叫成功,將至少分配所需記憶體.若實際分配量超過所需,則記憶體仍然能夠充分利用之.可用函式 GlobalSize 來確定實際所分配的位元組數,可使用 GlobalFree 來釋放記憶體。

4.LocalAlloc
該函式用於從區域性堆中分配記憶體供程式使用,函式原型為:

HLOCAL LocalAlloc(
  UINT uFlags, //指定怎樣去分配記憶體
  UINT uBytes, //指定要分配的位元組數
);

引數說明:
1.uFlags,指定怎樣去分配記憶體。如果zero被指定,預設的是LMEM_FIXED標誌。此引數有三種標誌:LMEM_FIXED(分配固定記憶體,返回值是指向一個記憶體物件的指標),LMEM_ZEROINIT(初始化記憶體內容為zero),LPTR(結合了LMEM_FIXED和LMEM_ZEROINIT這兩種標誌),LMEM_MOVEABLE(分配可移動記憶體),LMEM_DISCARDABLE(分配可刪除的記憶體)。
2.uBytes,指定要分配的位元組數。
小結:
在16位Windows中是有區別的,因為在16位windows用一個全域性堆和區域性堆來管理記憶體,每一個應用程式或dll裝入記憶體時,程式碼段被裝入全域性 堆,而系統又為每個例項從全域性堆中分配了一個64kb的資料段作為該例項的區域性堆,用來存放應用程式的堆疊和所有全域性或靜態變數。而 LocalAlloc/GlobalAlloc就是分別用於在區域性堆或全域性堆中分配記憶體。
由於每個程式的區域性堆很小,所以在區域性堆中分配記憶體會受到空間的限制。但這個堆是每個程式私有的,相對而言分配資料較安全,資料訪問出錯不至於影響到整個系統。
而在全域性堆中分配的記憶體是為各個程式共享的,每個程式只要擁有這個記憶體塊的控制程式碼都可以訪問這塊記憶體,但是每個全域性記憶體空間需要額外的記憶體開銷,造成分配浪費。而且一旦發生嚴重錯誤,可能會影響到整個系統的穩定。
不過在Win32中,每個程式都只擁有一個省缺的私有堆,它只能被當前程式訪問。應用程式也不可能直接訪問系統記憶體。所以在Win32中全域性堆和區域性堆都 指向程式的省缺堆。用LocalAlloc/GlobalAlloc分配記憶體沒有任何區別。甚至LocalAlloc分配的記憶體可以被 GlobalFree釋放掉。所以在Win32下程式設計,無需注意Local和Global的區別,一般的記憶體分配都等效於 HeapAlloc(GetProcessHeap(),...)。
LocalAlloc對應的釋放函式為LockFree。

5.Malloc
malloc與free是C++/C語言的標準庫函式,可用於申請動態記憶體和釋放記憶體。對於非內部資料型別的物件而言,光用 malloc/free無法滿足動態物件的要求。物件在建立的同時要自動執行建構函式,物件在消亡之前要自動執行解構函式。由於malloc/free是 庫函式而不是運算子,不在編譯器控制許可權之內,不能夠把執行建構函式和解構函式的任務強加於malloc/free。

6.New
是C++的運算子,主要用來新建類,與C++的建構函式和異常機制有關,與上述其它函式的使用環境大相庭徑。一般編譯器中的new都是用malloc來分配記憶體的。
New對應的釋放函式為delete。

三、拷貝/複製記憶體函式

1.memcpy
函式原型:
void *memcpy(void *destin, void *source, unsigned n);
引數:

  • destin-- 指向用於儲存複製內容的目標陣列,型別強制轉換為 void* 指標。
  • source-- 指向要複製的資料來源,型別強制轉換為 void* 指標。
  • n-- 要被複制的位元組數。

2.CopyMemory
函式原型:

VOID CopyMemory(
  PVOID Destination,
  CONST VOID *Source,
  SIZE_T Length
);

引數:

  • Destination-- 要複製記憶體塊的目的地址。
  • Source-- 要複製記憶體塊的源地址。
  • Length-- 指定要複製記憶體塊的大小,單位為位元組

3.RtlCopyMemory
CopyMemory一樣
函式原型:

void RtlCopyMemory(
   void*       Destination,
   const void* Source,
   size_t      Length
);

引數:

  • Destination-- 要複製記憶體塊的目的地址。
  • Source-- 要複製記憶體塊的源地址。
  • Length-- 指定要複製記憶體塊的大小,單位為位元組

4.RtlMoveMemory
函式原型:

VOID RtlMoveMemory(
  VOID UNALIGNED *Destination,
  const VOID UNALIGNED *Source,
  SIZE_T Length
);

引數:
Destination-- 指向移動目的地址的指標。
Source-- 指向要複製的記憶體地址的指標。
Length-- 指定要複製的位元組數。

相關文章