转载请表明出处可能可以获得完整审计源码~
随着近年来虚拟化技术飞速发展,使用虚拟化工具的人数日趋增加,同时孕育了大量相关产业。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文件中找到记录。