首页
社区
课程
招聘
[原创] Windows .lib 文件后门注入
2023-4-1 09:59 18705

[原创] Windows .lib 文件后门注入

2023-4-1 09:59
18705

Windows *.lib文件后门注入

lib文件在windows下有两种形式出现,第一种就是普通的静态库,第二种是作为dll的导入库。

 

接下来我来分享一下如何在这两种lib文件中注入后门代码,使程序编译后生成的exe在运行时候自动

 

执行我们后门,并且不影响正常lib的功能。

1. 注入原理

首先大致说一下lib的文件格式,用压缩文件可以打开lib文件,里面可以看到许多的obj文件(几个cpp就有几个obj)。

 

 

lib实际上就是一堆Obj文件打包在了一起,当然还有一些额外的信息,这个之后再说。

 

 

只要我们把我们的后门程序backdoor.lib 和 正常的lib合并到一起,这样我们的后门代码就有机会被执行。

 

但是这样做还有几个问题

1. 包含后门代码的Obj文件在生成exe的过程中会被链接吗?

如果obj中有符号被引用的话,这个obj才会被链接。否则不会。

2. 如果obj被链接进去 ,怎么做才能使程序启动时自动执行我们的代码。

在我们的后门代码中写一个全局对象,利用全局对象的构造函数,使它在main等函数之前自动执行。

2.实现

现在我们要解决的问题是,想办法使程序链接我们的后门代码的obj。

 

首先来了解一下obj文件格式

 

 

这里我们只分析这种的coff格式,在研究的过程中还发现了另一种 Microsoft CLI ByteCode 的obj文件,我们这里不讨论。(vs编译时候选择链接时生成代码,最后生成的lib文件中包含的obj就是这种)

 

COFF格式总览:

 

 

这个相比于PE格式还是简单了很多。我们可以使用CoffViewer这个工具来查看obj文件:

 

 

我们可以在原本的lib文件中的每一个obj文件中加入一段代码,在这段代码中引用后门代码中的函数。这样程序在编译的时候,在处理正常的obj文件时发现引用了一个外部符号,最终在包含后门代码的obj文件中找到了,这样这个obj就会被链接进程序。

 

虽然obj文件格式不算复杂,但是相比于上面这种方法还有一种更简单的方法,就是直接在SymbolTable 里面加入一个外部符号即可,(这个外部符号的定义需要在我们的后门代码中)。

 

笔者在实验的过程中发现,只要添加一个外部符号,不论有没有使用到这个符号,都无法通过编译。

 

所以我们现在的目标就是在符号表里面添加一项。

 

相关的数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
typedef struct _FILEHEADER
{
    unsigned short machine;     // 平台名
    unsigned short numberOfSections;// 区段数
    unsigned long  timeDateStamp;   // 时间戳
    unsigned long  pointerToSymbolTable;        // 符号表文件偏移
    unsigned long  numberOfSymbols;                // 符号总个数
    unsigned short sizeOfOptionalHeader;        // 可选头长度
    unsigned short characteristics;                // 文件标记
 
} FILEHEADER, *PFILEHEADER;
 
/*
最大为8个字节的,以’\0’为结尾的ASCII字符串.用于记录区段的名字
.区段的名字有些是特定意义的区段. 如果区段名的数量大于8个字节,
则name的第一字节是一个斜杠字符:’/’,接着就是一个数字,
这个数字就是字符串表的一个索引.它将索引到一个具体的区段名.
*/
 
typedef struct  _SECTIONHEADER
{
    char name[8];                            // 段名
    unsigned long  virtualSize;                 // 虚拟大小  没有用
    unsigned long  virtualAddress;             // 虚拟地址 没有用
    unsigned long  sizeOfRawData;             // 区段数据的字节数
    unsigned long  pointerToRawData;         // 区段数据偏移
    unsigned long  pointerToRelocations;     // 区段重定位表偏移
    unsigned long  pointerToLinenumbers;     // 行号表偏移
    unsigned short  numberOfRelocations;     // 重定位表个数
    unsigned short  numberOfLinenumbers;     // 行号表个数
    unsigned long  characteristics;          // 段标识
}SECTIONHEADER, *PSECTIONHEADER;
 
#pragma pack(push, 2)
typedef struct _SYMBOL
{
    union {
        char name[8];               // 符号名称
        struct {
            unsigned long zero;     // 字符串表标识
            unsigned long offset;  // 字符串偏移
        };
    };
    unsigned long value;            // 符号值
    short          section;          // 符号所在段
    unsigned short type;            // 符号类型
    unsigned char Class;            // 符号存储类型
    unsigned char numberOfAuxSymbols;// 符号附加记录数
} SYMBOL, *PSYMBOL;
#pragma pack(pop)
 
typedef struct _STRIGTABLE{
    unsigned int Size;
    char         Data[1];
} STRIGTABLE, *PSTRIGTABLE;

我们可以通过FileHeader找到符号表的位置,然后在符号表的后面 与 字符串表之前再插入一项。

 

注意:

  1. FileHeader里面的NumberOfSymbols 包含了numberOfAuxSymbols.
  2. 字符串表 = FileHeader→numberOfSymbols * sizeof(SYMBOL) + FileHeader→pointerToSymbolTable
  3. 一个auxSymbol和SYMBOL大小相同,都是18个字节。

先从原本的lib中提取出obj文件,然后在修改了obj之后,重新打包为lib (利用lib程序)。这时候编译就无法通过了,会提示链接时找不到符号。这时候把包含后门代码的obj也打包到这个lib里面,就能通过编译了,而且包含后门的obj文件也会被链接进去。

3. DLL导入库注入后门代码

前文提到lib有两种,一种是普通的lib文件,一种是dll的导入库。网上都是这么说的。

 

然而在研究的过程中,笔者发现这两种lib根本没有啥区别,只不过dll导入库里面的obj文件不包含代码段。而且dll的导入库里面还包含了一些其他的数据(也是COFF文件,但不是本文提到的Intel 80386 COFF object file)。

 

既然里面也包含obj文件,那么上文的方法能否也同样适用于这种lib文件,答案是可以的。

 

但是我们无法将该lib文件里面的数据用lib解压出来重新打包,因为lib程序只能打包obj文件。

 

有一种办法就是,手动解析lib文件,遍历其成员,如果是intel 80386 COFF Object File,我们就在它的符号表里面加一项。

lib文件格式分析:

lib文件格式还是很简单的,下面笔者带大家分析一下lib的格式

lib格式总览

  1. 8个字节的magic "!<arch>\n”
  2. 后面跟着许多的members

    1. 每一个member的开头是一个header:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      struct ar_hdr {
       char ar_name[16];
       char ar_date[12];
       char ar_uid[6];
       char ar_gid[6];
       char ar_mode[8];
       char ar_size[10];
       char ar_fmag[2];
      };

      都是字符串形式,但是要注意的是这些字符串未使用的部分不是字节’\x00’,而是空格符。

      其中的ar_size指出和后面跟着的数大小,如果这个数是个奇数,后面的数据对齐到偶数,以’\x0a’ 去补齐。

      b. 之后跟着的就是文件数据

Member分析:

每一个成员开头都有一个这样的header:

1
2
3
4
5
6
7
8
9
struct ar_hdr {
    char ar_name[16];
    char ar_date[12];
    char ar_uid[6];
    char ar_gid[6];
    char ar_mode[8];
    char ar_size[10];
    char ar_fmag[2];
};

ar_name以字符’/’ 开头,接着跟着是文件的名称。

 

如果ar_name 为’//’ 的话,这意味着这是一个目录信息,它后面的数据信息中包含了该目录下所有的文件的名称 。(它的数据区是一个字符串表)

 


假设数据区包含了N条路径信息。 那么之后的N个Members是具体的文件,并且每一个Member的name都是’/’ + 文件名称在字符串表中的偏移

 

 

大部分的Member都是用来描述lib内包含的obj文件,但是lib的前两个Member是比较特殊的。经过对比多个lib文件后,笔者终于猜测出前两个Member数据区的格式:

 

第一个Member:

  1. ar_name 为 ‘/’
  2. 数据区的格式:
1
2
3
4
DWORD NumberOfSymbols;  //该lib文件中的符号个数
DWORD ObjOffset;        //每个符号所在的obj 在lib文件中的偏移
                        //(注意是obj文件在lib中的偏移,Member的偏移)
//注意上面这些数据都是按大端方式存储
  1. 字符串表,包含NumberOfSymbols个字符串。

第二个Member:

  1. ar_name为 ‘/’
  2. 数据区的格式:
1
2
3
4
DWORD NumberOfObjs;                      //lib文件中包含的coff
DWORD OffsetOfObjs[NumberOfObjs];        //每一个coff文件在lib文件中的偏移
DWORD NumberOfSymbols;                   //符号个数
WORD  index[NumberOfSymbols];            //每一个符号位于第几个obj里面,从1开始
  1. 字符串表,包含NumberOfSymbols个字符串。

好了,在知道了lib文件格式之后,我们可以随便修改里面的包含的obj文件了。在修改了lib之后,再利用lib程序将正常的lib和包含后门代码的静态库链接到一起就可以了。

 

这里就不详细说明具体做法了。

总结

前文dll导入库的注入方法中是通过手动修改lib文件,这种方法也是适用于一般的lib静态库的。

 

在手动修改lib文件之后,然后利用lib程序将正常的lib文件 与我们的后门程序打包到一起。这样就成功的将后门注入到lib中了。

 

笔者在实验过程中还发现了一些问题:

  1. c++代码有二进制兼容性问题,不同版本的编译器生成的lib文件无法使用。如果我们的后门程序是C++实现的话,如果用户在编译时候使用了被注入后门的lib文件, 这会导致用户在编译时候无法正常通过。所以我们的后门程序最好是用c语言去实现。而且尽量不要调用c语言运行库内的函数,最好使用windows 原生的API。

  2. 如果是使用c语言的话,现在又出现了一个问题,如何在mian之前执行我们的后门代码?在gcc里面是有arrtibute属性的,但是msvc里面恐怕不行。这里笔者提供两种方法 (来源于网络):

    1. C语言

      1
      2
      3
      #pragma data_seg(".CRT$XIU")    
      void * _ctor_ = func;
      #pragma data_seg()

b. C++

1
2
3
#pragma data_seg(".CRT$XCU")    
void * _ctor_ = func;
#pragma data_seg()

注意如果是C语言的话,func格式返回0代表成功,非0代表失败,这回导致程序退出。

效果展示:

dll文件:

1
2
3
4
5
6
#include <stdio.h>
 
extern "C" __declspec(dllexport)
void add(int a,int b){
    printf("Hello World\n");
}

编译之后生成两个文件: TestDll.dll和TestDll.lib

 

用压缩软件看一下 TestDll.lib

 

 

实际上有四个文件,里面的txt是前文提到的前两个Member

 

我们使用CoffViewer查看一下第一个TestDll.dll文件

 

 

(可以思考一下为什么符号表是这些东西)

 

接着修改这个TestDll.lib,可以看到,写进去三个外部符号

 


看一下TestDll2.lib:

 

 

看起来是没啥区别的,提取出第一个TestDll.dll来看一下:

 

 

这时候已经多了一个符号了。

 

vs编译一下看看效果:

 

 

现在当然不会通过编译,因为这个符号是定义在我们的后门代码里面的。

 

利用lib程序把后门和正常的lib文件打包到一起:

 

 

重新编译运行,可以看到后门程序上线:

 

 

参考博客:

 

COFF文件格式 - 拖鞋搭袜 - 博客园 (cnblogs.com)


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2023-4-3 21:59 被_emmm_编辑 ,原因:
上传的附件:
收藏
点赞16
打赏
分享
最新回复 (15)
雪    币: 11312
活跃值: (4038)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xie风腾 2023-4-1 11:21
2
0

虽然看不懂,但是很强大哟
雪    币: 1123
活跃值: (1644)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_lpcoesnt 2023-4-1 18:07
3
0
感谢大佬分享
雪    币: 222
活跃值: (1866)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
lhglhg 1 2023-4-1 22:28
4
0
这样的方式,能防得住?
雪    币: 22
活跃值: (423)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
靴子 2023-4-2 10:11
5
0
研究的很深入!
雪    币: 6207
活跃值: (3107)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zhatian 2023-4-3 09:14
6
0
编译总是错误,无用的,垃圾文章一篇,浪费时间。
雪    币: 318
活跃值: (213402)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
shinratensei 1 2023-4-3 10:36
7
0
tql
雪    币: 462
活跃值: (708)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
_emmm_ 2023-4-3 12:00
8
4
编译不通过建议学学链接的知识,别基础知识都不会就想搞lib注入。
雪    币: 225
活跃值: (1487)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_0xC05StackOver 2023-4-3 16:23
9
1
这个用在供应链攻击中非常有用 尤其是在拿下打包机权限的时候 在中间产物中加上后门 可比从源代码层面加上隐蔽多了 尤其是开发机打包只是build不rebuild的情况下 
雪    币: 6207
活跃值: (3107)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zhatian 2023-4-3 18:23
10
0
_emmm_ 编译不通过建议学学链接的知识,别基础知识都不会就想搞lib注入。
供应链攻击啊,我看你快吃牢饭了。 
雪    币: 320
活跃值: (1758)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
轻装前行 2023-4-5 12:18
11
1
易语言脚本小子直呼害怕
雪    币: 21
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
归墟ker 2023-4-13 09:54
12
0
雪    币: 341
活跃值: (1166)
能力值: ( LV3,RANK:24 )
在线值:
发帖
回帖
粉丝
少妇之友 2023-4-19 14:47
13
0
感谢分享
雪    币: 6124
活跃值: (4081)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
黑洛 1 2023-5-7 03:11
14
0

你想多了,现在打包都是ci

最后于 2023-5-7 03:12 被黑洛编辑 ,原因:
雪    币: 5163
活跃值: (3250)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
樂樂 2023-5-7 12:09
15
0
感谢分享 可惜没看明白
雪    币: 2264
活跃值: (1508)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
CatF1y 2 2023-5-22 20:19
16
0
emmm ddw
游客
登录 | 注册 方可回帖
返回