分类 默认分类 下的文章

使用LD_PRELOAD保护个人信息

引言

在现在这个时代,想要个人信息完全不暴露,几乎是不可能的。

不管用的哪个软件,只要TA跑起来,TA就可以读取电脑上的很多文件。

那么在Uubntu上有没有什么办法,可以控制程序一些文件能读而另一些文件不能读呢?

其实是有的。比如:文件权限,但文件权限控制太僵硬了。有时不给读,程序就不起来。

我们这个文章就介绍一种比较温柔的方法:允许程序读,但是对于某些文件读取的内容是脱敏后的。

LD_PRELOAD

在ubuntu系统,程序读取文件一般都是先调用(直接或间接)open或fopen函数来打开文件。

int open(const char *pathname, int flags, ... /* mode_t mode */ );
FILE *fopen(const char *restrict pathname, const char *restrict mode);

只要我们想办法,让在程序调用open或fopen前,

修改一下pathname的路径,就可以把程序要读取的文件改成其它文件。

从而保护原来文件不被读取。

而ld.so正好提供环境变量LD_PRELOAD,可以帮助我们实现上述功能。

LD_PRELOAD列的共享库文件(.so)中的函数,将被程序优先调用。

所以我们需要作的就是,自己实现一个版本open和fopen函数。TA们的功能是:

当被调用时,检查一下pathname是否为敏感文件,如果是则修改pathname为不敏感版本的文件,

然后再调用原始版本open和fopen打开文件返回。

代码(path_rename.c)参考如下:

#include <dlfcn.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>

extern int vsnprintf(char *str, size_t size, const char *format, va_list ap);
extern int snprintf(char *str, size_t size,
                   const char *restrict format, ...);

static inline ssize_t print(const char*fmt,...){
    char buf[4096] = {0};
    va_list ap;

    va_start(ap, fmt);
    int n = vsnprintf(buf, sizeof(buf), fmt, ap);
    va_end(ap);

    return write(2,buf,n);
}

static const char *PATH_MAP[][2] = {
    /**
    {"需要保护的文件路径","实际上允许读取的文件路径"}
    **/
    { "/etc/lsb-release", "/etc/lsb-release.pub" },
    { "/etc/issue", NULL},/** NULL -> /etc/issue.pricacy **/
    { "for_test", NULL}, /** NULL -> for_test.pricacy **/
};

static const int PATH_MAP_LEN = sizeof(PATH_MAP) / sizeof(PATH_MAP[0]);

static inline void fix_path(const char*path,char *out_path,size_t out_len)
{
        if(out_len<0)return;
        out_path[0] = 0;
        if(path == NULL)return;

        //默认:维持pathname路径不变
        strncpy(out_path,path,out_len);

        for(int i=0;i<PATH_MAP_LEN;i++){
                const char*o = PATH_MAP[i][0];
                if(o == NULL)continue;
                const char*n = PATH_MAP[i][1];
                if(strcmp(path,o) == 0){
                        if(n == NULL){
                                snprintf(out_path,out_len,"%s.privacy",path);
                        }else{
                                strncpy(out_path,n,out_len);
                        }
                        print("%s -> %s\n",o,out_path);
                        break;
                }
        }
}

int open (const char * pathname, int flags, ...)
{
    print("%s:%s(%s,%d)\n",__FILE__,__func__,pathname,flags);

    char path[4096] = {0};
    fix_path(pathname,path,sizeof(path));
    static int (*open_ptr)(const char* , int) = NULL;
    if(open_ptr == NULL){
        open_ptr = dlsym(RTLD_NEXT,__func__);
    }
    int fd = open_ptr(path,flags);
    return fd;
}

void *fopen(const char *pathname, const char *mode){
    print("%s:%s(%s,%d)\n",__FILE__,__func__,pathname,mode);
    char path[4096] = {0};
    fix_path(pathname,path,sizeof(path));
    static void* (*open_ptr)(const char* , const char *) = NULL;
    if(open_ptr == NULL){
        open_ptr = dlsym(RTLD_NEXT,__func__);
    }
    void* fd = open_ptr(path,mode);
    return fd;
}

我们把新实现open和fopen编译成共享库(path_rename.so):

gcc -shared -fpic path_rename.c -o path_rename.so -ldl -D_GNU_SOURCE

写个简单代码(main.c),用来测试:

#include <stdio.h>

int main(int argc,char **argv)
{
    if(argc>1){
        FILE *fp = fopen(argv[1],"rb");
        if(fp){
            char buf[4097] = {0};
            size_t n = 0;
            while(!feof(fp)){
                n = fread(buf,1,sizeof(buf)-1,fp);
                //printf("n: %ld\n",n);
                buf[n] = 0;
                printf("%s",buf);
            }
            fclose(fp);
        }
    }
    return 0;
}

再写个简单Makefile:

main:main.c
    $(CC) $< -o $@  -g3 -Wall

path_rename.so:path_rename.c
    $(CC) -shared -fpic $< -o $@ -ldl -D_GNU_SOURCE

clean:
    rm -rf *.so main

test:main path_rename.so Makefile
    LD_PRELOAD=./path_rename.so ./main main.c || true
    LD_PRELOAD=./path_rename.so cat for_test /etc/lsb-release || true

然后

make test

就可以立马看到效果了。

用法

修改path_rename.c中的PATH_MAP,再

make path_rename.so

然后

LD_PRELOAD=dir_of_so/path_rename.so xxxx

这样程序xxxx,能读什么文件就随我们拿捏了。

注意

函数open和fopen,只是比较常见的用于打开文件的函数。

打开文件还有其TA函数可选择,具体可自行查抄。

但针对其TA函数,实现文件保护的实现套路是一样一样的。

Ubuntu 18.04 在root文件上,系统启用lvmcache

虽然18.04有点老,但是还有再用。且还遇到了问题,所以就写写

1 root文件系统必须是lvm的逻辑卷

具体如何创建lvm逻辑卷,搜一下,应该很容易能找到。所以这里省略了......

2 /boot使用独立分区

/boot分区一般给个500+MiB就差不多够用了。
为什么要这样呢?据说是因grub读不了lvmcache(我没试过,但其他人的说法我信了),
如果不独立分区的话,/boot中的内核、grub、initrd就都在lvmcache上了,
grub无法读取这些数据,引导不了系统。

3 安装必要程序

    sudo apt install -y thin-provisioning-tools

4 启用lvmcache

首先保存如下代码:
#!/bin/bash

ssd=$2 # pv 用于作为缓存,存储空间(可以整个磁盘/磁盘分区)
vg=$(basename $(dirname "$3"))
lv=$(basename "$3")

function cache(){
    local ssd_size meta_size cache_size
    ssd_size=$(pvs ${ssd} --units m|grep -v PSize |awk '{print $5}'|sed 's|\.[0-9]*m||g')
    meta_size=$(((${ssd_size}/800)*8))
    cache_size=$(( ((${ssd_size}-${meta_size}*2)/8)*8 ))

    vgextend ${vg} ${ssd}
    #lvcreate -n cache -L ${cache_size}MiB ${vg} ${ssd} && \
    #lvcreate -n cachemeta -L ${meta_size}MiB ${vg} ${ssd} && \
    #lvconvert --type cache-pool --poolmetadata ${vg}/cachemeta ${vg}/cache && \
    #lvconvert --type cache --cachepool ${vg}/cache --cachemode writeback ${vg}/$lv
    lvcreate --type cache-pool -n cache -L ${cache_size}MiB ${vg} ${ssd} && \
    lvconvert --type cache --cachepool ${vg}/cache --cachemode writeback ${vg}/$lv
}

function vg_name()
{
    pvdisplay "$1"|grep 'VG Name'|awk '{print $3}'
}

function uncache()
{
    vg0=$(vg_name ${ssd})
    test "x${vg0}" = "x${vg}" || {
        echo "bad pv"
        exit 1
    }
    lvconvert --uncache ${vg}/$lv && {
        lvremove ${vg}/cachemeta
        lvremove ${vg}/cache
        vgreduce ${vg} ${ssd}
    }
}

function usage()
{
    echo "$0 [cache | uncache] cache_pv vg/lv"
    exit 0
}

( test -z "$1" || test -z "$2" || test -z "$vg" || test "$vg" = "." || test -z "$lv" || test "$lv" = "." ) && usage

lvp=/dev/${vg}/${lv}
test -b ${lvp} || {
    echo "$lvp is not exist"
    exit 1
}

test -b ${ssd} || {
    echo "$ssd is not exist"
    exit 2
}

$1
假设代码保存为lvmcache,挂在到root文件系统的lvm逻辑卷为/dev/root/ubuntu,
用于缓存的磁盘分区为/dev/sddX,为/dev/root/ubuntu,启用cache:
    bash lvmcache cache /dev/sddX /dev/root/ubuntu
然后一路按'y'就可以了。

5 更新initrd

因为ubuntu 18.04的thin-provisioning-tools程序包,
没自动配置initrd支持lvmcache。所以我们需要手动搞一下,代码如下(copy from 23.10):
#!/bin/sh

PREREQ=""

prereqs()
{
    echo "$PREREQ"
}

case $1 in
prereqs)
    prereqs
    exit 0
    ;;
esac

. /usr/share/initramfs-tools/hook-functions

copy_exec /usr/sbin/pdata_tools
ln -s pdata_tools ${DESTDIR}/usr/sbin/cache_check
ln -s pdata_tools ${DESTDIR}/usr/sbin/thin_check

manual_add_modules dm-cache
manual_add_modules dm-cache-smq
manual_add_modules dm-thin-pool
保存到:/usr/share/initramfs-tools/hooks/thin-provisioning-tools
更新initrd,命令如下:
sudo update-initramfs -k all -u -t

重启系统

如果能够顺利,回归系统,说明root文件系统启用lvmcache成功了。

Ubuntu系统,如何让dkms编译的内核模块支持安全启动?

1 在BIOS中打开SecureBoot支持

这因为不同品牌的计算机设置不尽相同,就不做详细说明了。
根据自己计算机的实际情况,搜一下基本上都能找方法。
或者找一下购买计算机附赠的手册,一般也能找到开启/关闭安全启动的方法

在Ubuntu系统中,查看SecureBoot的状态:

mokutil --sb-state

2 安装必要软件包

sudo apt-get install --yes shim-signed mokutil

3 创建签名内核模块需要的密钥

sudo update-secureboot-policy --new-key

生成了两文件:

ls /var/lib/shim-signed/mok/
MOK.der  MOK.priv

MOK.der --- 公钥

MOK.priv --- 密钥

4 公钥导到安全启动的信任列表

sudo mokutil --import /var/lib/shim-signed/mok/MOK.der

这一步会要求设置一个密码,可随意设个简单密码就行。

注意:这个命令仅是做一个“导入密钥的计划”,真正导入需要重启一下,进行实操。

5 重启计算机导入公钥

重启后就进入了一个操作界面,分别选择:

Enroll MOK -> continue -> yes -> password -> reboot

password 就是上一步import时,设置的密码

注意:

如果这个环节有哪一步选错了,可以重复4,5

6 内核模块签名

搞了上面的操作之后,dkms再编译完内核模块,

就会自动使用/var/lib/shim-signed/mok下的密钥,

对生成的内核模块进行签名

所以只需要重新编译一些内核模块即可:

for m in $(dkms status |sed 's|[/:]|,|g;s|[ \t]||g' |awk -F, '{print $1"/"$2}' |sort |uniq)
do
    dkms remove ${m} --all
    for v in /lib/modules/*
    do
        test -d "${v}/initrd" || continue
        v=$(basename $v)
        dkms install ${m} -k ${v}
    done
done

7 完成

此后,就用dkms辅助编译内核模块(自动签名),SecureBoot不再是个麻烦。
例如:
    sudo apt-get install --yes nvidia-driver-xxx
    sudo apt-get install --yes virtualbox

docker build 遇到:

* xxx (Y/I/N/O/D/Z) [default=N] ? dpkg: error processing package xxx (--configure):
end of file on stdin at conffile prompt

怎么办?
给apt-get install 或 apt-get upgrade,添加参数

-o Dpkg::Options::="--force-confold"

强制保留当前的配置文件即可

关闭睿频

sudo sh -c "echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo"

设置CPU最大频率到60%

sudo sh -c "echo 60 > /sys/devices/system/cpu/intel_pstate/max_perf_pct"