1. 序言
供應(yīng)鏈攻擊是一種傳播間諜軟件的方式,一般通過產(chǎn)品軟件官網(wǎng)或軟件包存儲庫進(jìn)行傳播。通常來說,黑客會(huì)瞄準(zhǔn)部署知名軟件官網(wǎng)的服務(wù)器,篡改服務(wù)器上供普通用戶下載的軟件源代碼,將間諜軟件傳播給前往官網(wǎng)下載軟件的用戶。在實(shí)施攻擊時(shí),有一種方式是通過污染上游廠商的編譯環(huán)境來攜帶攻擊者的惡意載荷。
在污染編譯環(huán)境時(shí),對污染文件的選擇上,需要關(guān)注兩個(gè)重點(diǎn),第一,要確保被污染分代碼能夠被編譯進(jìn)目標(biāo)程序,第二,需要足夠隱蔽,防止被安全工具檢測到。在Windows環(huán)境下,通過替換MSVC的C標(biāo)準(zhǔn)運(yùn)行庫的方式可以同時(shí)達(dá)到上訴兩種要求,下面詳細(xì)介紹該技術(shù)的實(shí)現(xiàn)細(xì)節(jié)。
2. 環(huán)境與工具
靶場Windows靶機(jī) 任意版本visualstudio MSVC組件
3. MSVC組件
MSVC全稱Microsoft Visual C++,是微軟公司的免費(fèi)C++開發(fā)工具,具有集成開發(fā)環(huán)境,可提供編輯C語言,C++以及C++/CLI等編程語言。VC++集成了便利的除錯(cuò)工具,特別是集成了微軟Windows視窗操作系統(tǒng)應(yīng)用程序接口(Windows API)、三維動(dòng)畫DirectX API,Microsoft .NET框架。可以通過打開Visual Studio Installer查看MSVC組件文件目錄,需要污染的C標(biāo)準(zhǔn)運(yùn)行庫就是在該目錄下。
MSVC有多個(gè)版本,可以通過設(shè)置中的平臺工具集確認(rèn)自己當(dāng)前使用的版本,由于筆者當(dāng)前使用的版本是v143,即14.3。
4. C標(biāo)準(zhǔn)運(yùn)行庫-MSVCRT.LIB
1. 什么是MSVCRT.LIB
Visual Studio使用的CRT靜態(tài)庫文件為MSVCRT.LIB,CRT全程為C runtime Library,意義為Windows的C標(biāo)準(zhǔn)運(yùn)行庫,初始CRT的代碼位于多個(gè)庫文件中,大多數(shù)軟件發(fā)布時(shí)使用運(yùn)行庫的動(dòng)態(tài)多線程RELEASE版本,該種方式在編譯時(shí)需要用到MSVCRT.LIB。
圖:MSVC的CRT初始化庫
如果對程序進(jìn)行調(diào)試,觀察程序的調(diào)用堆棧,會(huì)發(fā)現(xiàn)程序并非是從編寫的main函數(shù)開始執(zhí)行的,這是因?yàn)?a target="_blank">開發(fā)者編寫C代碼時(shí),入口點(diǎn)雖然為main函數(shù),但是在程序運(yùn)行時(shí),程序真正的入口點(diǎn)為mainCRTStartup函數(shù),在編譯時(shí)會(huì)將MSVCRT.LIB的內(nèi)容鏈接到開發(fā)者自定義代碼之前。
2. MSVCRT.LIB的路徑
上文中,我們介紹過MSVC組件的路徑,并確定使用的版本為14.3,MSVCRT.LIB文件及其源碼均在該目錄下。源碼文件在crtsrcvcruntime中,MSVCRT.LIB在lib文件下存在多種版本,本文使用x86下的MSVCRT.LIB進(jìn)行演示。
3. 程序的執(zhí)行流程
在程序運(yùn)行時(shí),真正的入口點(diǎn)為mainCRTStartup函數(shù),該程序被定義在exe_main.cpp中,其源碼如下。
#define_SCRT_STARTUP_MAIN #include"exe_common.inl" extern"C"DWORD mainCRTStartup(LPVOID) { return__scrt_common_main(); }
可以看到mainCRTStartup調(diào)用了scrt_common_main,根據(jù)include我們可以知道scrt_common_main被定義在了exe_common.inl中,exe_common.inl的部分代碼如下。
static__declspec(noinline) int__cdecl __scrt_common_main_seh() { if(!__scrt_initialize_crt(__scrt_module_type::exe)) __scrt_fastfail(FAST_FAIL_FATAL_APP_EXIT); boolhas_cctor = false; __try { boolconstis_nested = __scrt_acquire_startup_lock(); if(__scrt_current_native_startup_state == __scrt_native_startup_state::initializing) { __scrt_fastfail(FAST_FAIL_FATAL_APP_EXIT); } elseif(__scrt_current_native_startup_state == __scrt_native_startup_state::uninitialized) { __scrt_current_native_startup_state = __scrt_native_startup_state::initializing; if(_initterm_e(__xi_a, __xi_z) != 0) return255; _initterm(__xc_a, __xc_z); __scrt_current_native_startup_state = __scrt_native_startup_state::initialized; } else { has_cctor = true; } __scrt_release_startup_lock(is_nested); // If this module has any dynamically initialized __declspec(thread) // variables, then we invoke their initialization for the primary thread // used to start the process: _tls_callback_type const* consttls_init_callback = __scrt_get_dyn_tls_init_callback(); if(*tls_init_callback != nullptr&& __scrt_is_nonwritable_in_current_image(tls_init_callback)) { (*tls_init_callback)(nullptr, DLL_THREAD_ATTACH, nullptr); } // If this module has any thread-local destructors, register the // callback function with the Unified CRT to run on exit. _tls_callback_type const* consttls_dtor_callback = __scrt_get_dyn_tls_dtor_callback(); if(*tls_dtor_callback != nullptr&& __scrt_is_nonwritable_in_current_image(tls_dtor_callback)) { _register_thread_local_exe_atexit_callback(*tls_dtor_callback); } // // Initialization is complete; invoke main... // intconstmain_result = invoke_main(); // // main has returned; exit somehow... // if(!__scrt_is_managed_app()) exit(main_result); if(!has_cctor) _cexit(); // Finally, we terminate the CRT: __scrt_uninitialize_crt(true, false); returnmain_result; } __except (_seh_filter_exe(GetExceptionCode(), GetExceptionInformation())) { // Note:We should never reach this except clause. intconstmain_result = GetExceptionCode(); if(!__scrt_is_managed_app()) _exit(main_result); if(!has_cctor) _c_exit(); returnmain_result; } } // This is the common main implementation to which all of the CRT main functions // delegate (for executables; DLLs are handled separately). static__forceinline int__cdecl __scrt_common_main() { // The /GS security cookie must be initialized before any exception handling // targeting the current image is registered. No function using exception // handling can be called in the current image until after this call: __security_init_cookie(); return__scrt_common_main_seh(); }
觀察源碼我們可以看到scrt_common_main中調(diào)用了安全cookie與scrt_common_main_seh,scrt_common_main_seh中通過invoke_main到達(dá)用戶定義的main函數(shù),其調(diào)用如下圖所示。
上訴內(nèi)容執(zhí)行早于main函數(shù),所以污染MSVCRT.LIB文件可以確保注入的代碼必然被執(zhí)行,又因上訴函數(shù)調(diào)用非敏感函數(shù),通常安全工具不會(huì)對其進(jìn)行檢測,因此保證了隱蔽性。
5. 編譯環(huán)境污染
1. 文件編譯
在源碼中,我們挑選一個(gè)文件進(jìn)行代碼注入,本文選擇dyn_tls_init.c進(jìn)行演示,dyn_tls_init.c的源碼如下。
// // dyn_tls_init.c // // Copyright (c) Microsoft Corporation. All rights reserved. // // This source file provides a fallback definition of __dyn_tls_init_callback, // used whenever TLS initialization is not required. // // This relies on a feature of the C compiler known as "communal variables." // This does not work in C++, and the linker's alternatename features is not // sufficient here. // #include#pragmawarning(disable: 4132) // const object should be initialized constPIMAGE_TLS_CALLBACK __dyn_tls_init_callback; PIMAGE_TLS_CALLBACK const* __cdecl __scrt_get_dyn_tls_init_callback() { return&__dyn_tls_init_callback; }
添加一些自定義代碼
#include#pragmawarning(disable: 4132) // const object should be initialized constPIMAGE_TLS_CALLBACK __dyn_tls_init_callback; add(intx,inty){ returnx + y; } PIMAGE_TLS_CALLBACK const* __cdecl __scrt_get_dyn_tls_init_callback() { intx = 1; inty = 2; intz = add(x, y); return&__dyn_tls_init_callback; }
關(guān)閉編譯優(yōu)化選項(xiàng),此過程不可省略,CTRL+F7編譯;
將編譯好的obj文件拷貝出來。
2. 清理原始o(jì)bj
obj文件就是c文件編譯之后產(chǎn)生的一種文件,一個(gè)c文件編譯之后只會(huì)產(chǎn)生一個(gè)obj文件,一個(gè)lib文件是obj文件的集合,當(dāng)然,其中還夾雜著其他一些輔助信息,目的是為了讓編譯器能夠準(zhǔn)確找到對應(yīng)的obj文件,這些文件一起通過AR打包。我們需要找到這些輔助信息完成LIB文件中obj目標(biāo)文件的替換。obj文件可以利用類似7z工具進(jìn)行解包,解壓后如下。
在目錄中有兩個(gè)文本文件,里面記錄了obj文件對應(yīng)信息,搜索dyn_tls_init.obj,搜索到D:a_work1sIntermediatecrtvcstartupuildmdmsvcrt_kernel32msvcrt_kernel32.nativeprojobjrx86dyn_tls_init.obj,該值為dyn_tls_init.obj打包時(shí)文件的路徑。刪除MSVCRT.LIB文件中dyn_tls_init.obj的相關(guān)信息,刪除obj需要link.exe工具,該工具在MSVC的bin目錄下,刪除指令如下。
link-lib"XX:XXXmsvcrt.lib" -remove:D:a\_work1sIntermediatecrtvcstartupbuildmdmsvcrt_kernel32msvcrt_kernel32.nativeprojobjrx86dyn_tls_init.obj
3. 寫入新編譯obj
在清理完原始o(jì)bj文件后,將新編譯好的obj文件寫入lib文件,解壓lib文件后,可以發(fā)現(xiàn)除了文本文件外,還有一個(gè)文件夾名為D_,該文件夾代表obj文件打包前所在的磁盤盤符,也就是D盤。在通過MSVC的工具進(jìn)行打包時(shí),會(huì)根據(jù)obj所在路徑創(chuàng)建對應(yīng)的文件,并將obj的路徑記錄到里面的文本文件中。也就是說新編譯好的文件不需要與原始o(jì)bj文件在相同目錄下,但是為了看起來更加完美,建議根據(jù)文本文件中的地址為編譯好的obj文件創(chuàng)建相同的路徑。寫入lib文件需要用到lib.exe工具,使用指令如下。
lib "XX:XXXmsvcrt.lib""D:a\_work1sIntermediatecrtvcstartupuildmdmsvcrt_kernel32msvcrt_kernel32.nativeprojobjrx86dyn_tls_init.obj”
寫入obj后,新建一個(gè)C/C++的項(xiàng)目,隨意編寫一些代碼。
編譯后使用反匯編工具查看main之前的代碼,發(fā)現(xiàn)寫入dyn_tls_init.obj的功能已經(jīng)被編譯到工程中,反匯編代碼如下。
call___scrt_release_startup_lock pop ecx callsub_401CD0 mov esi, eax xoredi, edi cmp [esi], edi jz shortloc_401783 push esi call___scrt_is_nonwritable_in_current_image pop ecx testal, al jz shortloc_401783 mov esi, [esi] push edi push 2 push edi mov ecx, esi callds:___guard_check_icall_fptr callesi callsub_401D0B mov esi, eax cmp [esi], edi jz shortloc_4017A1 push esi call___scrt_is_nonwritable_in_current_image pop ecx testal, al jz shortloc_4017A1 push dword ptr [esi] ; Callback call_register_thread_local_exe_atexit_callback pop ecx call_get_initial_narrow_environment mov edi, eax call__p___argv mov esi, [eax] call__p___argc push edi ; envp push esi ; argv push dword ptr [eax] ; argc call_main
##注入的內(nèi)容 #sub_401CD0 pushebp movebp, esp subesp, 0Ch mov[ebp+var_8], 1 mov[ebp+var_4], 2 moveax, [ebp+var_4] pusheax movecx, [ebp+var_8] pushecx callsub_401D00 addesp, 8 mov[ebp+var_C], eax moveax, offsetunk_405380 movesp, ebp popebp retn #sub_401D00 pushebp movebp, esp moveax, [ebp+arg_0] addeax, [ebp+arg_4] popebp retn endp
6. 總結(jié)
通過上訴例子可以看出,污染CRT靜態(tài)庫文件的方式簡單易用,且具有很高的隱蔽性,當(dāng)攻陷對方編譯服務(wù)器時(shí),該方式危害極大,因其與源碼一同被編譯到項(xiàng)目中,會(huì)攜帶合法簽名。希望在大家在了解其工作原理后,不僅收獲了一種新的代碼劫持技能,在分析惡意軟件時(shí)也多了一個(gè)思路。
丈八網(wǎng)安蛇矛實(shí)驗(yàn)室成立于2020年,致力于安全研究、攻防解決方案以及靶場仿真復(fù)現(xiàn)等相關(guān)方向。團(tuán)隊(duì)核心成員均由從事安全行業(yè)10余年經(jīng)驗(yàn)的安全專家組成,團(tuán)隊(duì)目前成員涉及紅藍(lán)對抗、滲透測試、逆向破解、病毒分析、工控安全以及免殺等相關(guān)領(lǐng)域。
審核編輯:劉清
-
CRT技術(shù)
+關(guān)注
關(guān)注
0文章
5瀏覽量
6660 -
匯編語言
+關(guān)注
關(guān)注
14文章
410瀏覽量
35856 -
C++語言
+關(guān)注
關(guān)注
0文章
147瀏覽量
7009 -
CLI
+關(guān)注
關(guān)注
1文章
79瀏覽量
8572
原文標(biāo)題:供應(yīng)鏈攻擊之編譯環(huán)境
文章出處:【微信號:蛇矛實(shí)驗(yàn)室,微信公眾號:蛇矛實(shí)驗(yàn)室】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論