一種通用DLL劫持技術(shù)研究

通用DLL劫持技術(shù)研究

by anhkgg

2018年11月29日

寫在前面

Dll劫持相信大家都不陌生赴涵,理論就不多說了焦履。Dll劫持的目的一般都是為了自己的dll模塊能夠在別人進程中運行,然后做些不可描述的事情矗烛。

為了讓別人的程序能夠正常運行休里,通常都需要在自己的dll中導出和劫持的目標dll相同的函數(shù)接口,然后在自己的接口函數(shù)中調(diào)用原始dll的函數(shù)琉朽,如此使得原始dll的功能能夠正常被使用毒租。導出接口可以自己手工寫稚铣,也可以通過工具自動生成,比如著名的Aheadlib墅垮。這種方法的缺點就是針對不同的dll需要導出不同的接口惕医,雖然有工具幫助,但也有限制算色,比如不支持x64抬伺。

除此之外,很早之前就知道一種通用dll劫持的方法灾梦,原理大致是在自己的dll的dllmian中加載被劫持dll峡钓,然后修改loadlibrary的返回值為被劫持dll加載后的模塊句柄。這種方式就是自己的dll不用導出和被劫持dll相同的函數(shù)接口若河,使用更加方便能岩,也更加通用。

下面就嘗試分析一下如何實現(xiàn)這種通用的dll劫持方法萧福。

原理分析

隨便寫一個測試代碼:

//mydll.dll 偽造的用于劫持mydll.dll的dll代碼
//mydll.dll.1是把test.exe加載的原始dll修改為這個名字
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        __debugbreak();
        HMODULE hmod = LoadLibraryW("mydll.dll.1");
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
//test.exe
void main()
{
    LoadLibraryW(L"mydll.dll");
}

用windbg加載看看堆棧拉鹃,如下所示。在test中通過LoadLibraryW加載mydll.dll,最后進入mydll!DllMain「嘌啵現(xiàn)在需要分析系統(tǒng)映射dll之后是如何把基地址返回給LoadLibraryW钥屈,然后才能想辦法把這個值給修改成加載mydll.dll.1的值。

0:000> kvn
 # ChildEBP RetAddr  Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
00 0025eaf8 6e4112ec 6e410000 00000000 00000000 mydll+0x101d
01 0025eb38 6e4113c9 6e410000 00000001 00000000 mydll+0x12ec
02 0025eb4c 77d889d8 6e410000 00000001 00000000 mydll!DllMain+0x13
03 0025eb6c 77d95c41 6e4113ad 6e410000 00000001 ntdll!LdrpCallInitRoutine+0x14
04 0025ec60 77d9052e 00000000 74e92d11 77d77c9a ntdll!LdrpRunInitializeRoutines+0x26f (FPO: [Non-Fpo])
05 0025edcc 77d9232c 0025ee2c 0025edf8 00000000 ntdll!LdrpLoadDll+0x4d1 (FPO: [Non-Fpo])
06 0025ee00 75ee88ee 0037429c 0025ee40 0025ee2c ntdll!LdrLoadDll+0x92 (FPO: [Non-Fpo])
07 0025ee38 761b3c12 00000000 00000000 00000001 KERNELBASE!LoadLibraryExW+0x15a (FPO: [Non-Fpo])
08 0025ee4c 6848e3f5 0025ee58 003a0043 0055005c kernel32!LoadLibraryW+0x11 (FPO: [Non-Fpo])
09 0025f068 6848d1de d9131536 00000000 00000000 test!start+0x2b5
0a 0025f09c 6848e245 013a0000 761b3c26 76b3ea5f test!start+0x21e86e
0b 0025f328 013a1918 013a0000 0037187a 00000000 test!start+0x105
0c 0025fb44 013a30b9 013a0000 00000000 0037187a test+0x1918
0d 0025fb90 761b3c45 7ffd9000 0025fbdc 77d937f5 test+0x30b9
0e 0025fb9c 77d937f5 7ffd9000 74e93b01 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
0f 0025fbdc 77d937c8 013a312b 7ffd9000 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])
10 0025fbf4 00000000 013a312b 7ffd9000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

先去reactos翻看一下坝辫,找到如下的函數(shù)調(diào)用結(jié)構(gòu)篷就。在LdrLoadDll參數(shù)中BaseAddress就是最后返回給LoadLibraryW的值,所以繼續(xù)看BaseAddress是如何賦值的近忙。BaseAddress繼續(xù)傳給LdrpLoadDll腻脏,在LdrpLoadDll中,首先通過LdrpMapDll映射dll模塊银锻,返回一個LdrEntry的LDR_DATA_TABLE_ENTRY結(jié)構(gòu)永品,保存了dll加載的基址、大小击纬、名字等信息鼎姐。接著LdrEntry會插入到peb->ldr鏈表結(jié)構(gòu)中,然后調(diào)用LdrpRunInitializeRoutines更振,在LdrpRunInitializeRoutines中最終會調(diào)用DllMain炕桨,此處不繼續(xù)深入分析。最后LdrEntry->DllBase賦值給BaseAddress肯腕。到此流程分析清楚献宫,下面考慮如何修改這個值。

NTSTATUS
NTAPI
LdrLoadDll(IN PWSTR SearchPath OPTIONAL,
           IN PULONG DllCharacteristics OPTIONAL,
           IN PUNICODE_STRING DllName,
           OUT PVOID *BaseAddress) {
               Status = LdrpLoadDll(RedirectedDll,
                         SearchPath,
                         DllCharacteristics,
                         DllName,
                         BaseAddress,
                         TRUE);
           }

NTSTATUS
NTAPI
LdrpLoadDll(IN BOOLEAN Redirected,
            IN PWSTR DllPath OPTIONAL,
            IN PULONG DllCharacteristics OPTIONAL,
            IN PUNICODE_STRING DllName,
            OUT PVOID *BaseAddress,
            IN BOOLEAN CallInit)
            {
                Status = LdrpMapDll(DllPath,
                            DllPath,
                            NameBuffer,
                            DllCharacteristics,
                            FALSE,
                            Redirected,
                            &LdrEntry);

                 //插入peb->ldr鏈表

                Status = LdrpRunInitializeRoutines(NULL);

                if (NT_SUCCESS(Status))
                {
                    /* Return the base address */
                    *BaseAddress = LdrEntry->DllBase;
                }
            }    

LdrpRunInitializeRoutines-> LdrpCallInitRoutine -> DllMain

記得映像中的那種方法实撒,是通過堆楁⑼荆回溯到LdrpLoadDll中,找到LdrEntry進行修改(不確實是否準備知态,時間久遠了)捷兰,但因為LdrEntry是局部變量,不同系統(tǒng)可以不一樣负敏,兼容性差一些贡茅。但看到這個調(diào)用流程之后,其實還有另一種方式其做。LdrEntry->DllBase賦值給BaseAddress顶考,那么在賦值之前把這個LdrEntry->DllBase修改了即可,在DllMain正好是修改的時機妖泄,但是不需要使用堆椌匝兀回溯的方式。因為LdrEntry已經(jīng)插入到peb->ldr中浮庐,那么在DllMain中可以直接獲取peb->ldr遍歷鏈表找到目標dll堆棧的LdrEntry就是需要修改的LdrEntry甚负,然后修改即可柬焕。

不過這個分析都是基于reactos來的,還是需要確認一下真是windows系統(tǒng)的ntdll是如何首先的梭域。

在win7 x64系統(tǒng)中斑举,ntdll的關(guān)鍵代碼如下所示。差別是LdrpLoadDll直接返回的ldrentry病涨,而不是BaseAddress富玷,在LdrpLoadDll內(nèi)部流程基本和reactos一致。所以方案應該可行既穆,后續(xù)驗證確實證明可行赎懦。

int __fastcall LdrLoadDll()
{
v11 = LdrpLoadDll(v5, v9, v10, 1, 0i64, &dataentry);
  v12 = v11;
  if ( v11 >= 0 )
    *dllbase = dataentry->DllBase;

}

嘗試實現(xiàn)

實現(xiàn)其實非常簡單,關(guān)鍵代碼如下所示幻工。兩部分代碼励两,一個是加載原始dll模塊(mydll.dll.1)拿到真是的模塊句柄hMod(基地址),第二個就是遍歷peb->ldr找到mydll.dll的ldrentry囊颅,然后修改dllbase為hMod当悔。

void* NtCurrentPeb()
{
    __asm {
        mov eax, fs:[0x30];
    }
}
PEB_LDR_DATA* NtGetPebLdr(void* peb)
{
    __asm {
        mov eax, peb;
        mov eax, [eax + 0xc];
    }
}
VOID SuperDllHijack(LPCWSTR dllname, HMODULE hMod)
{
    WCHAR wszDllName[100] = { 0 };
    void* peb = NtCurrentPeb();
    PEB_LDR_DATA* ldr = NtGetPebLdr(peb);

    for (LIST_ENTRY* entry = ldr->InLoadOrderModuleList.Blink;
        entry != (LIST_ENTRY*)(&ldr->InLoadOrderModuleList);
        entry = entry->Blink) {
        PLDR_DATA_TABLE_ENTRY data = (PLDR_DATA_TABLE_ENTRY)entry;

        memset(wszDllName, 0, 100 * 2);
        memcpy(wszDllName, data->BaseDllName.Buffer, data->BaseDllName.Length);

        if (!_wcsicmp(wszDllName, dllname)) {
            data->DllBase = hMod;
            break;
        }
    }
}
VOID DllHijack(HMODULE hMod)
{
    TCHAR tszDllPath[MAX_PATH] = { 0 };

    GetModuleFileName(hMod, tszDllPath, MAX_PATH);
    PathRemoveFileSpec(tszDllPath);
    PathAppend(tszDllPath, TEXT("mydll.dll.1"));

    HMODULE hMod1 = LoadLibrary(tszDllPath);

    SuperDllHijack(L"mydll.dll", hMod1);
}
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        DllHijack(hModule);
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

總結(jié)

經(jīng)測試在win7 x84和win10 x64中即是有效的,其他系統(tǒng)未測試踢代,如果有問題盲憎,請留言或自行解決。

害怕這種方案不行胳挎,還想了另一種思路饼疙,在dllmain中hook LdrpLoadDll的返回調(diào)用地址處,修改dataentry的值慕爬,因為LdrLoadDll函數(shù)接口固定窑眯,所以這種方式也應該是通用的,不過實現(xiàn)起來其實還比現(xiàn)在的麻煩些澡罚,所以只是保留了這種思路伸但,并未去實現(xiàn)驗證肾请,留給愛折騰的朋友吧留搔。

最后,代碼上傳了github铛铁,https://github.com/anhkgg/SuperDllHijack

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末隔显,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子饵逐,更是在濱河造成了極大的恐慌括眠,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件倍权,死亡現(xiàn)場離奇詭異掷豺,居然都是意外死亡捞烟,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門当船,熙熙樓的掌柜王于貴愁眉苦臉地迎上來题画,“玉大人,你說我怎么就攤上這事德频〔韵ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵壹置,是天一觀的道長竞思。 經(jīng)常有香客問我,道長钞护,這世上最難降的妖魔是什么盖喷? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮难咕,結(jié)果婚禮上传蹈,老公的妹妹穿的比我還像新娘。我一直安慰自己步藕,他們只是感情好惦界,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著咙冗,像睡著了一般沾歪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上雾消,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天灾搏,我揣著相機與錄音,去河邊找鬼立润。 笑死狂窑,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的桑腮。 我是一名探鬼主播泉哈,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼破讨!你這毒婦竟也來了丛晦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤提陶,失蹤者是張志新(化名)和其女友劉穎烫沙,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體隙笆,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡锌蓄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年升筏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瘸爽。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡仰冠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蝶糯,到底是詐尸還是另有隱情洋只,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布昼捍,位于F島的核電站识虚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏妒茬。R本人自食惡果不足惜担锤,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望乍钻。 院中可真熱鬧肛循,春花似錦、人聲如沸银择。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浩考。三九已至夹孔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間析孽,已是汗流浹背搭伤。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留袜瞬,地道東北人怜俐。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像邓尤,于是被迫代替她去往敵國和親拍鲤。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容