LD_PRELOAD应用--基于libvirt审计(上)

时间:2022-11-08 07:58:04


    转载请表明出处可能可以获得完整审计源码~

    随着近年来虚拟化技术飞速发展,使用虚拟化工具的人数日趋增加,同时孕育了大量相关产业。libvirt虚拟化审计就是在这个背景下产生的。

    libvirt提供了统一抽象的虚拟化管理平台---libvirtd服务器,通过他可以与主流的虚拟化平台交互,例如QEMU/KVM等, 将用户虚拟机请求发送给特定具体的虚拟化介质,由该虚拟化介质实现虚拟机的操作。同时libvirt也向用户提供了虚拟化管理API,让用户与libvirtd服务器交互,其中virsh/virt-manager就是基于API开发的客户端。


    本文的主题,libvirt审计就是分别以LD_PRELOAD截获libvirtd注册虚拟化驱动实现服务器审计;截获libvirt API实现客户端审计。本文先简单的介绍客户端审计的实现。

    客户端审计的流程如下:

    1).查看libvirt.so导出的函数

    2).自己实现同名函数(包括相同的参数/返回值),并在同名函数中通过dlopen/dlsym获得libvirt.so中导出符号的地址

    3).记录参数和拦截操作

    4).对于合法的操作,将参数传递给原函数;对于非法的操作,直接返回错误值

    5).获得原函数的返回值,记录;

    以下是对以上步骤的具体实现:

    1).查看客户端工具virsh依赖的libvirt的路径及导出的符号:

>which virsh
/usr/bin/virsh
>ldd /usr/bin/virsh
libvirt.so=>/usr/lib64/libvirt.so.0
>nm -D /usr/lib64/libvirt.so.0
T virConnectOpen
T virDomainSuspend

     从上面的命令来看,virsh依赖的库函数为/usr/lib64/libvirt.so.0,该库函数导出了众多符号,这里仅列举和实现virConnectOpen/virDomainSuspend

    2).查看libvirt/virsh.c源码发现:当客户端用默认方式连接libvirtd时,会调用virConnectOpen获得连接句柄,以后客户端对libvirtd的操作都以此作为标识;当客户端需要暂定虚拟机的运行,则会调用virDomainSuspend。他们的接口声明为:

int virDomainSuspend(virDomainPtr domain);
virConnectPtr virConnectOpen (const char *URI);

由于virConnectPtr和virDomainPtr的类型都已在/usr/include/libvirt/libvirt.h中作出声明,因此只需直接引用,不用做特别的操作。我们要做的是:

    2-1):申明函数指针,定义该指针变量,用于存放libvirt.so中导出的函数;

    2-2):dlopen/dlsym获得函数地址;

#include <dlfcn.h>
#include "/usr/include/libvirt/libvirt.h"

#define LIBVIRTPATH "/usr/lib64/libvirt.so.0"
#define DETOURLOGPATH "/root/detour.log"

#define ClearShareBuff() \
do{ \
memset(auditParam.auditLogContext,0,MAX_CONTENT_LEN); \
}while(0); \

#define WriteShareBuff() \
do{ \
(*auditCBFunc)(&auditParam); \
}while(0); \

//宏框架
#define detour_FILTER {
#define detour_FILTEREND }

#define detour_CALLORIG {
#define detour_CALLORIGEND }

#define detour_AUDIT {
#define detour_AUDITEND }

#define detour_PROLOG(addr,type) \
do{ \
if(addr!=NULL) \
break; \
addr = (type)dlsym(dllHnd, __func__); \
assert(addr != NULL); \
}while(0); \

#define detour_EPILOG(res) \
do{ \
return res; \
}while(0); \

<pre name="code" class="cpp">typedef int (*detour_virDomainSuspend)(virDomainPtr);
typedef virConnectPtr (*detour_virConnectOpen)(const char*);


//在自己的.so文件中申明同名virConnectOpen,当so文件被LD_PRELOAD注入到virsh后,会覆盖原有的virConnectOpen

//如此,virsh调用virConnectOpen连接libvirtd时,会进入我们实现的代码中

virConnectPtr virConnectOpen (const char *URI)

{

virConnectPtr res = NULL;

injectValidProc = 1;

//调用dlsym 获得真正的virConnectOpen的地址

detour_PROLOG(virConnectOpenAddr,detour_virConnectOpen);


//实现api拦截,对于不合法的用户,可以直接在此返回

detour_FILTER

detour_FILTEREND



//调用真正的virConnectOpen函数,并把参数传递给它

detour_CALLORIG

res = (*virConnectOpenAddr)(URI);

detour_CALLORIGEND



//开始审计,记录参数并输出到日志

detour_AUDIT

ClearShareBuff();

sprintf(auditParam.auditLogContext,

"<methodResponse><event>%s</event><state>%d</state><param><uri>%s</uri></param></methodResponse>\n",

"virConnectOpen",(res!=NULL)?1:0,URI);

WriteShareBuff();

detour_AUDITEND



detour_EPILOG(res);

}


int virDomainSuspend(virDomainPtr domain)

{

int res = 0;

char name[4096]={0};



detour_PROLOG(virDomainSuspendAddr,detour_virDomainSuspend);



detour_FILTER

detour_FILTEREND



detour_CALLORIG

res = (*virDomainSuspendAddr)(domain);

detour_CALLORIGEND



detour_AUDIT

ClearShareBuff();



GetDomainName(domain);

sprintf(auditParam.auditLogContext,

"<methodResponse><event>%s</event><state>%d</state><param>name:%s</param></methodResponse>\n",

"virDomainSuspend",res,name);



WriteShareBuff();

detour_AUDITEND



detour_EPILOG(res);

}


以上实现了基本的功能,但还有一些初始化个功能尚待完成:

__attribute ((constructor)) void detour_init(void)
{
char logPath[4096] = {0};
pthread_t tid;
//sprintf(logPath,"%s%s-%d.log",DETOURLOGPATH,"detour",getpid());

fp = fopen(DETOURLOGPATH, "a+");
dllHnd = dlopen(LIBVIRTPATH,RTLD_LAZY|RTLD_GLOBAL);
assert(fp != NULL);
assert(dllHnd != NULL);
auditInitilize();
auditParam.fp = fp;
auditCBFunc = audit2LogFile;

return;
}
__attribute ((destructor)) void detour_fini(void)
{
dlclose(dllHnd);
fclose(fp);
free(auditParam.auditLogContext);
return;
}

因为dlsym需要指定库的句柄,程序中大量使用了这个句柄。每次都打开关闭无异于是件麻烦事,因此在so程序的入口函数中打开这个句柄并存放在全局变量中。

可以通过strace跟踪virsh的启动情况,可以观察到:

>export LD_PRELOAD=/root/Desktop/libdetour.so
>strace virsh
execv(/usr/bin/virsh);
mmap(/root/Desktop/libdetour.so);

系统首先载入virsh的镜像,然后依次载入virsh依赖的so文件,最后运行virsh,并与virsh!init函数中执行libdetour.so的入口函数


    以后凡是virsh的连接和挂起虚拟机的操作都可以在/root/detour.log文件中找到记录。