一、背景
rsync二进制程序依赖外部库,由于安全问题,有时会单独升级依赖的外部库。另外为了防止因为栈溢出攻击导致服务器被黑,需要对rsync及其依赖的外部库重新编译,开启安全编译选项,增加黑客破解的复杂度。
所有的库编译必须要求加上如下编译选项:
- 栈保护(-fstack-protector-all/-fstack-protector-strong)
- 堆栈不可执行(-Wl,-z,noexecstack)
- GOT表保护(-Wl,-z,relro)
- 地址无关代码/地址无关可执行(-fPIC/-fPIE -pie)
- 立即加载(-Wl,-z,now)
- strip
- 指定动态库搜索路径(-Wl,--disable-new-dtags,–rpath,./)
栈保护选项,编译器如果支持strong则开启 -fstack-protector-strong,编译器如果不支持则开启-fstack-protector-all
-Wl,--disable-new-dtags,–rpath,./ 指定动态库搜索路径,根据实际引用的库的相对路径配置,如果组件不依赖其它库则不需要
二、操作步骤
1、 rsync依赖的开源库
(1) 通过 ldd rsync 命令查看 rsync二进制软件依赖的库
远程到安装rsync的服务器上,执行 ldd rsync,查看rsync依赖哪些库。
通过上图的执行结果,可以看到 rsync 依赖的开源库包括如下:
- libattr
- libacl
- libz
- liblz4
- libzstd
- libxxhash
- libcrypto
(2) 访问 rsync的github代码库,查看其说明再次确认依赖的库情况
访问地址:https://github.com/RsyncProject/rsync/blob/master/INSTALL.md
从上面可以看到官方介绍依赖的开源库是:
- acl
- xattr
- xxhash
- zstd
- lz4
- openssl crypto
和 ldd 查看比较发现缺少一个libz,而这个对应的是zlib,观察rsync的代码库可以发现其内置了zlib,但是我们需要安全编译,也方便日后可以单独升级 zlib,因此需要关闭不使用其内置的zlib,直接依赖外部的zlib库。
总结以上,rsync依赖的开源库如下
- acl
- xattr
- xxhash
- zstd
- lz4
- openssl crypto
- zlib
2、 准备工作
2.1 准备编译服务器
准备一台编译服务器,其上已经安装了gcc,用于开源库编译,这里以 10.33.43.42服务器为例,该服务器为一台x64结构服务器。
2.2 下载所有开源库源码
2.2.1 下载 rsync 开源库源码
- 官网地址 https://rsync.samba.org/download.html
- 进入github https://github.com/RsyncProject/rsync/releases
这里选择 3.3.0 版本。
2.2.2 下载 Acl 开源库源码
- 远程到编译服务器
- 执行yum info acl
可以看到其中有两个版本,第一个地址访问不了,所以选择第二个地址
点击 download 可以看到所有能下载的源码:
https://download.savannah.nongnu.org/releases/acl/
选择下载最新 2.3.2 版本。
2.2.3 下载 xxHash 开源库源码
https://github.com/Cyan4973/xxHash
https://github.com/Cyan4973/xxHash/releases
点击 右下角的 release链接可以看到所有的发布版本
这里选择 0.8.2 版本
2.2.4 下载 attr 开源库源码
- 在编译服务器上通过yum info attr方式,发现找不到它的rmp文件,基于此,从网上直接查找
- 网络搜索 libattr.so 官网,可以找到如下
- 直接访问https://savannah.nongnu.org/projects/attr
- 点击上图的download,也就是地址:http://download.savannah.nongnu.org/releases/attr/
这里选择使用 2.5.2 版本。
2.2.5 下载 zstd 开源库源码
- 在 rsync 的install.cmd 上直接点击 zstd 的链接,跳转到 zstd 官网
- 直接在官网上点击github
- Github上点击 右下角的 release 链接可以看到多个发布版本
这里选择 1.5.6 版本
2.2.6 下载 lz4 开源库源码
- 在 rsync 的install.cmd 上直接点击 lz4的链接,跳转到lz4官网
- 进入lz4官网:https://lz4.org/
- 进入github:https://github.com/lz4/lz4
这里选择 1.10.0版本
2.2.7 下载 zlib 开源库源码
2.2.8 下载 openssl 开源库源码
这里使用 openssl 3.0.15 版本
3、 开源库编译
通过第二章节,准备的开源库源码包或者已经编译好的库如下:
3.1 准备工作
远程到编译服务器,创建如下目录
/opt/wf
|-- openssl
|--rsync
|--include
|--lib
- 将本地准备的openssl(已经编译好的)目录下的所有目录和文件上传到编译服务器/opt/wf/openssl目录下;
- 将本地准备的 libz.so.1 文件上传到编译服务器/opt/wf/rsync/lib目录下;
- 将本地准备的所有tar.gz包上传到编译服务器/opt/wf/rsync/include目录下;
3.1 attr开源库编译
远程到编译服务器,执行以下命令
- 解压源码
cd /opt/wf/rsync/include tar -zxvf attr-2.5.2.tar.gz mv attr-2.5.2 attr |
- ./configure
cd attr ./configure CFLAGS="-fstack-protector-all-fPIC" CPPFLAGS="-fstack-protector-strong -fPIC " LDFLAGS="-pie -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,--disable-new-dtags,-rpath,./" |
- make
- make install
可以看到so文件被安装到了/usr/lib目录下,编译好的库文件为libattr.so.1.1.2502
- 将libattr.so.1.1.2502拷贝并重命名到指定目录下
cp /usr/lib/libattr.so.1.1.2502 /opt/wf/rsync/lib/libattr.so.1
3.2 acl 开源库编译
远程到编译服务器,执行以下命令
- 解压源码
cd /opt/wf/rsync/include tar -zxvf acl-2.3.2.tar.gz mv acl-2.3.2 acl |
- ./configure
cd acl ./configure CFLAGS="-fstack-protector-all-fPIC" CPPFLAGS="-fstack-protector-strong -fPIC " LDFLAGS="-pie -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now-Wl,--disable-new-dtags,-rpath,./" |
- make
- make install
可以看到so文件被安装到了/usr/lib目录下,编译好的库文件为libacl.so.1.1.2302
- 将libacl.so.1.1.2302拷贝并重命名到指定目录下
cp /usr/lib/libacl.so.1.1.2302 /opt/wf/rsync/lib/libacl.so.1
3.3 xxHash 开源库编译
xxHash 源码中没有提供configure,已经存在MakeFile文件,所以需要将编译选项直接作为make 的参数传递进去。
远程到编译服务器,执行以下命令
- 解压源码
cd /opt/wf/rsync/include tar -zxvf xxHash-0.8.2.tar.gz mv xxHash-0.8.2 xxHash cd xxHash |
- make
make CFLAGS="-fstack-protector-all-fPIC" CPPFLAGS="-fstack-protector-strong -fPIC " LDFLAGS="-shared -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now-Wl,--disable-new-dtags,-rpath,./" |
- make install
可以看到xxHash目录下有编译好的so文件libxxhash.so.0.8.2
- 将libxxhash.so.0.8.2拷贝并重命名到指定目录下
cp libxxhash.so.0.8.2 /opt/wf/rsync/lib/libxxhash.so.0
3.4 zstd 开源库编译
zstd源码中没有提供configure,已经存在MakeFile文件,所以需要将编译选项直接作为make 的参数传递进去。
远程到编译服务器,执行以下命令
- 解压源码
cd /opt/wf/rsync/include tar -zxvf zstd-1.5.6.tar.gz mv zstd-1.5.6 zstd cd zstd |
- make
make CFLAGS="-fstack-protector-all-fPIC" CPPFLAGS="-fstack-protector-strong -fPIC " LDFLAGS="-shared -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now-Wl,--disable-new-dtags,-rpath,./" |
- make install
可以看到zstd/lib目录下有编译好的so文件libzstd.so.1.5.6
- 将libzstd.so.1.5.6拷贝并重命名到指定目录下
cp lib/libzstd.so.1.5.6 /opt/wf/rsync/lib/libzstd.so.1
3.5 lz4 开源库编译
Lz4源码中没有提供configure,已经存在MakeFile文件,所以需要将编译选项直接作为make 的参数传递进去。
远程到编译服务器,执行以下命令
- 解压源码
cd /opt/wf/rsync/include tar -zxvf lz4-1.10.0.tar.gz mv lz4-1.10.0 lz4 cd lz4 |
- make
make CFLAGS="-fstack-protector-all -fPIC -I/opt/wf/rsync/include/lz4/lib" CPPFLAGS="-fstack-protector-strong-fPIC -I/opt/wf/rsync/include/lz4/lib" LDFLAGS="-pie -Wl,-z,noexecstack-Wl,-z,relro -Wl,-z,now -Wl,--disable-new-dtags,-rpath,./" |
- make install
可以看到lz4/lib目录下有编译好的so文件liblz4.so.1.10.0
- 将liblz4.so.1.10.0拷贝并重命名到指定目录下
cp lib/liblz4.so.1.10.0 /opt/wf/rsync/lib/liblz4.so.1
3.6 rsync 开源库编译
远程到编译服务器,执行以下命令
- 解压源码
cd /opt/wf/rsync/include tar -zxvf rsync-3.3.0.tar.gz mv rsync-3.3.0 rsync cd rsync |
- 修改checksum.c 中的源码
将文件中的 EVP_MD_CTX_create 全部修改成 EVP_MD_CTX_new
- 指定openssl库的位置
先通过export 指定 openssl 的库路径,这里使用的是openssl 3 ,位置在/opt/wf/openssl
export LD_LIBRARY_PATH=/opt/wf/openssl/lib:$LD_LIBRARY_PATH |
- ./configure
./configure --disable-md2man--with-included-zlib=no --with-included-poptCFLAGS="-fstack-protector-all -fPIC -I/opt/wf/openssl/include" CPPFLAGS="-fstack-protector-strong-fPIC " LDFLAGS="-pie -Wl,-z,noexecstack-Wl,-z,relro -Wl,-z,now -Wl,--disable-new-dtags,-rpath,./ -L/opt/wf/openssl/lib -lssl -lcrypto" |
- 修改configure.status
将该文件拿到本地,搜索关键字SIZEOF_,将其内容修改成如下:
- make
可以看到编译好的rsync文件就在当前目录下
ldd rsync 可以看到这个二进制文件依赖的库
- 将openssl/lib下的libcrypto.so.3\libssl.so.3 以及 rsync 移动到 /opt/wf/rsync/lib下
cp rsync /opt/wf/rsync/lib/
cp /opt/wf/openssl/lib/libssl.so.3 /opt/wf/rsync/lib/
cp /opt/wf/openssl/lib/libcrypto.so.3 /opt/wf/rsync/lib/
- 进入到/opt/wf/rsync/lib 下,再次执行ldd rsync ,查看其依赖的库是否都在当前目录下(最好重新打开一个远程)
可以看到到此rsync编译完毕,只要将 /opt/wf/rsync/lib 下所有库和文件拿到本地即可,这样rsync 编译完成。
3.7 检查编译出来的库是否都是安全的
使用公共安全库提供的checksec.sh-2.7.1.zip 程序进行校验,将该包放置到编译服务器/opt/wf下,进行如下操作:
cd /opt/wf unzip checksec.sh-2.7.1.zip mv checksec.sh-2.7.1 check cd check ./checksec --dir=/opt/wf/rsync/lib #如果检查单个文件使用 --file=xxxx.so |
明显以上库是安全的。
四、问题与总结
1、Acl 编译报错提示缺失“attr/error_context.h”
在执行 ./configure 命令时,直接报错了,配置不通过,如下:
原因分析
提示缺失 attr/error_context.h 找不到,因此acl 依赖attr,需要先编译attr开源库和安装。
解决办法
先编译和安装attr开源库,再编译acl 开源库。
2、xxHash编译报错提示“undefined reference to main”
现象
在执行make(make CFLAGS="-fstack-protector-all -fPIC" CPPFLAGS="-fstack-protector-strong -fPIC " LDFLAGS="-pie -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,--disable-new-dtags,-rpath,./")命令时,直接报错,具体如下
解决办法
将make命令中的系列参数 –pie修改成 -shared
make CFLAGS="-fstack-protector-all -fPIC" CPPFLAGS="-fstack-protector-strong -fPIC " LDFLAGS="-shared -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,--disable-new-dtags,-rpath,./" 将-pie去掉换成-shared 才正常 (分析出来是这个原因是直接make可以成功,将直接make过程与添加编译选择的执行过程做了比较,发现需要改成shared)
3、zstd编译报错提示“undefined reference to main”
现象
在执行make(make CFLAGS="-fstack-protector-all -fPIC" CPPFLAGS="-fstack-protector-strong -fPIC " LDFLAGS="-pie -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,--disable-new-dtags,-rpath,./")命令时,直接报错,具体如下
解决办法
将make命令中的系列参数 –pie修改成 -shared
make CFLAGS="-fstack-protector-all -fPIC" CPPFLAGS="-fstack-protector-strong -fPIC " LDFLAGS="-shared -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,--disable-new-dtags,-rpath,./" 将-pie去掉换成-shared 才正常 (分析出来是这个原因是直接make可以成功,将直接make过程与添加编译选择的执行过程做了比较,发现需要改成shared)
4、 lz4编译报错提示“fatal error:xxhash.h:No such file or directory”
现象
在执行make(make CFLAGS="-fstack-protector-all -fPIC" CPPFLAGS="-fstack-protector-strong -fPIC " LDFLAGS="-pie -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,--disable-new-dtags,-rpath,./")命令时,直接报错,具体如下
根据错误提示,貌似是在提示找不到 xxhash.h 文件
分析
深入会发现xxhash.h 文件其实在 lz4的lib目录下,也就是说make时其无法自动搜索到lib目录,找到这个文件,为此编译时需要加上-I指定追加的文件搜索路径。
解决办法
make CFLAGS="-fstack-protector-all -fPIC -I/opt/wf/rsync/include/lz4/lib" CPPFLAGS="-fstack-protector-strong -fPIC -I/opt/wf/rsync/include/lz4/lib" LDFLAGS="-pie -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,--disable-new-dtags,-rpath,./"
在CFLAGS和CPPFLAGS上都加上-I/opt/wf/rsync/include/lz4/lib选项,在进行编译可以成功。
5、 rsync configure命令执行完之后,make报错“error:unknown type name ‘int32’”
现象
首先执行:./configure --disable-md2man --with-included-zlib=no --with-included-popt CFLAGS="-fstack-protector-all -fPIC" CPPFLAGS="-fstack-protector-strong -fPIC " LDFLAGS="-pie -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,--disable-new-dtags,-rpath,./"
再次执行make命令,执行的时候报如下错误
分析
在rsync github issue 上查到有人提过这个问题,查看作者的回复发现是因为rsync默认是32位上编译的,而我们当前编译服务器是64位操作系统,所以检测不通过,需要手动更改configure命令执行完成之后生成的config.status文件。
从图上可以看到这些值全是0,需要修改成正确的值。
解决方案
将config.status文件拿到本地,按照关键字“SIZEOF_”,将对应的key值修改成如下的值。
6、 rsync 编译需要指定openssl
现象
由于openssl 也需要重新安全编译,因此rsync只能依赖我们编译好的openssl,不能使用系统默认的openssl。对此,rsync编译的时候需要指定openssl,否则会出现如下错误:
配置的命令为
./configure --disable-md2man --with-included-zlib=no --with-included-popt CFLAGS="-fstack-protector-all -fPIC" CPPFLAGS="-fstack-protector-strong -fPIC " LDFLAGS="-pie -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,--disable-new-dtags,-rpath,./"
make 之后 生成的 rsync文件使用 ldd 查看依赖的库
提示:version ‘libcrypto.so.10’ not found (required by ./rsync)
通过查看资料和网上百度,需要在LDFLAGS上添加-lssl –lcrypto 表示使用指定的openssl 库进行编译,添加-L/opt/wf/openssl/lib表示在编译的时候从指定的openssl 的库目录加载库文件;在CFLAGS上添加-I/opt/wf/openssl11/openssl/include表示在编译的时候从指定的openssl的头文件目录加载需要的头文件。
因此 最终的configure命令调整成如下:
./configure --disable-md2man --with-included-zlib=no --with-included-popt CFLAGS="-fstack-protector-all -fPIC -I/opt/wf/openssl/include" CPPFLAGS="-fstack-protector-strong -fPIC " LDFLAGS="-pie -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,--disable-new-dtags,-rpath,./ -L/opt/wf/openssl/lib -lssl -lcrypto"
7、 rsync configure时提示“./configtest:error while loading shared libraries:libssl.so.1”
现象
执行了./configure --disable-md2man --with-included-zlib=no --with-included-popt CFLAGS="-fstack-protector-all -fPIC -I/opt/wf/openssl/include" CPPFLAGS="-fstack-protector-strong -fPIC " LDFLAGS="-pie -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,--disable-new-dtags,-rpath,./ -L/opt/wf/openssl/lib -lssl -lcrypto" 命令过程中,发现自行失败,提示的具体信息如下:
分析
通过界面提示的错误分析,发现 gcc 编译成功生成了configtest二进制文件,但是在执行的时候提示configtest需要libssl.so.1 库,但是加载不到这个库。在编译configtest二进制文件,虽然在编译的时候通过rpath指定了./ 相对路径,但是libssl.so.1 文件的确不在configtest 所属当前目录下,其在 /opt/wf/openssl/lib 下,因此想到使用export 的方式指定库搜索路径。
解决方案
为了解决上述找不到ssl库的问题,需要在执行configure命令之前,先通过export 指定 openssl 的库路径,export LD_LIBRARY_PATH=/opt/wf/openssl/lib
8、 rsync configure时提示”libssl.so:file format not recognized;treating as linker script”
现象
执行了./configure --disable-md2man --with-included-zlib=no --with-included-popt CFLAGS="-fstack-protector-all -fPIC -I/opt/wf/openssl/include" CPPFLAGS="-fstack-protector-strong -fPIC " LDFLAGS="-pie -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,--disable-new-dtags,-rpath,./ -L/opt/wf/openssl/lib -lssl -lcrypto" 命令过程中,发现自行失败,提示的具体信息如下:
分析
通过查找相关资料,发现是 openssl/lib 下的libssl.so 文件是 ASCII text 格式的
执行命令 file libssl.so
解决方案
不能在本地解压 openssl.zip 文件之后再上传到编译服务器上,需要将zip包直接上传到编译服务器上,然后通过 unzip 命令进行解压,才不会改变so文件的编码格式。
五、小知识点
1、查看so的编码信息
file xxx.so
2、编译时可指定 头文件路径
gcc xxx xxx -I/opt/wf/test/include
3、编译时可指定依赖库的所在路径
gc xxx xxx -L/opt/wf/test/lib
4、编译时可指定运行的时候从哪个目录搜索依赖的库
gc xxx xxx -Wl,-rpath=.
-Wl,-rpath=. 表示从运行文件的当前目录去搜索库