Last active
December 26, 2020 18:15
-
-
Save shinh/54abf74d1600cc506a632bd211501db0 to your computer and use it in GitHub Desktop.
bytecode polyglot - def con qual 2020 bytecoooding
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 | |
# elisp, lua, python2, and jvm | |
# https://docs.google.com/spreadsheets/d/1l1N_wtK8xA7N-ezG5iUjDeg6iKQgVaYf8ckTSp30QIo/ | |
$flag = File.read('flag').chomp | |
$ml_preamble = nil | |
$lua_preamble = nil | |
$ruby_preamble = nil | |
$node_preamble = '' | |
$jvm_preamble = nil | |
$python2_preamble = nil | |
$python3_preamble = nil | |
$elisp_preamble = nil | |
$ruby = "service/ruby-2.7.0/bin/ruby" | |
$bytenode = "bytenode" | |
$bytenode = "service/node-patched bytenode/cli.js" | |
def read_file(filename) | |
File.open(filename, 'r:binary') do |f| | |
f.read | |
end | |
end | |
def write_file(filename, s) | |
File.open(filename, 'w:binary') do |f| | |
f.write(s) | |
end | |
return filename | |
end | |
class BinaryConsumer | |
def initialize(b) | |
@b = b | |
@o = 0 | |
end | |
def read(n) | |
r = @b[@o, n] | |
@o += n | |
return r | |
end | |
def offset | |
@o | |
end | |
end | |
def run_ocaml(filename) | |
pipe = IO.popen("ocaml #{filename}", "r+") | |
pipe.close_write | |
return pipe.read | |
end | |
def check_ocaml(filename) | |
out = run_ocaml(filename) | |
if !out.include?($flag) | |
raise "No flag in #{out}" | |
end | |
end | |
def read_ocaml_int(code) | |
v = code[0].ord | |
if v >= 0x80 | |
raise "unknown int: #{v}" | |
elsif v < 0x40 | |
len = v.to_i + 1 | |
r = 0 | |
len.times do |i| | |
r <<= 8 | |
r |= code[i + 1].ord | |
end | |
return r, len | |
else | |
return v - 0x40, 1 | |
end | |
end | |
def ocaml_int_to_str(v) | |
if v < 64 | |
return [v + 0x40].pack("c") | |
end | |
if v < 128 | |
return [0, v].pack("c*") | |
end | |
if v < 4096 | |
return [1, v / 256, v % 256].pack("c*") | |
end | |
raise "not supported #{v}" | |
end | |
def forge_ml(offset) | |
preamble_size = 12 | |
cu_pos_off = 35 | |
cu_size_off = 4 | |
ml_source = %Q(print_endline (input_line (open_in "flag"))) | |
write_file('read_flag.ml', ml_source) | |
if !system("ocamlc read_flag.ml") | |
raise "ocamlc failed" | |
end | |
check_ocaml("read_flag.cmo") | |
c = read_file('read_flag.cmo') | |
$ml_preamble = preamble = c[0, preamble_size] | |
cu_pos = c[preamble_size, 4].unpack1("L>") | |
cu = c[cu_pos..-1] | |
cu_size = cu[cu_size_off, 4].unpack1("L>") | |
code_pos, code_pos_len = read_ocaml_int(cu[cu_pos_off..-1]) | |
code_size, code_size_len = read_ocaml_int(cu[cu_pos_off+code_pos_len..-1]) | |
code = c[code_pos, code_size] | |
STDERR.puts "[+] ocaml cu_pos=#{cu_pos} cu_size=#{cu_size} code_pos=#{code_pos} code_size=#{code_size}" | |
new_code_pos_len = ocaml_int_to_str(offset) | |
cu[cu_pos_off, code_pos_len] = new_code_pos_len | |
cu[cu_size_off, 4] = [cu_size + new_code_pos_len.size - code_pos_len].pack("L>") | |
out = preamble | |
out += [cu_pos + offset - code_pos].pack("L>") | |
out += [0].pack("c") * (offset - code_pos) | |
out += code | |
out += cu | |
write_file("out.cmo", out) | |
check_ocaml("out.cmo") | |
return out | |
end | |
def run_lua(filename) | |
pipe = IO.popen("lua5.2 #{filename}", "r+") | |
return pipe.read | |
end | |
def check_lua(filename) | |
out = run_lua(filename) | |
if !out.include?($flag) | |
raise "No flag in #{out}" | |
end | |
end | |
def forge_lua(num_skip) | |
preamble_size = 18 | |
code_off = 11 | |
lua_source = %Q(r=io.open"flag","r" | |
print(r:read())) | |
write_file('read_flag.lua', lua_source) | |
if !system("luac5.2 read_flag.lua") | |
raise "luac failed" | |
end | |
check_lua("luac.out") | |
c = read_file('luac.out') | |
$lua_preamble = preamble = c[0, preamble_size] | |
off = preamble_size + code_off | |
code_size = c[off, 4].unpack1("L") | |
off += 4 | |
bc = c[off, code_size * 4] | |
STDERR.puts "[lua] old bc_size=#{bc.size / 4}" | |
off += code_size * 4 | |
const_size = c[off, 4].unpack1("L") | |
off += 4 | |
rest = c[off..-1] | |
const_size.times do | |
typ = c[off].ord | |
off += 1 | |
if typ == 0 | |
STDERR.puts "[-] lua const nil" | |
elsif typ == 1 | |
STDERR.puts "[-] lua const bool" | |
off += 1 | |
elsif typ == 3 | |
STDERR.puts "[-] lua const number" | |
off += 8 | |
elsif typ == 4 | |
str_size = c[off, 8].unpack1("Q") | |
off += 8 | |
STDERR.puts "[-] lua const string #{c[off, str_size - 1]}" | |
off += str_size | |
else | |
raise "Unknown lua const type: #{typ}" | |
end | |
end | |
func_size = c[off, 4].unpack1("L") | |
off += 4 | |
upvalue_size = c[off, 4].unpack1("L") | |
off += 4 | |
off += upvalue_size * 2 | |
STDERR.puts "[+] lua code_size=#{code_size} const_size=#{const_size} upvalue_size=#{upvalue_size}" | |
out = c.dup | |
out[preamble_size, 4] = [0].pack("L") | |
out[preamble_size + 4, 4] = [0].pack("L") | |
# numparams | |
out[preamble_size + 8] = [0].pack('c') | |
# is_vararg | |
out[preamble_size + 9] = [0].pack('c') | |
# maxstacksize | |
out[preamble_size + 10] = [6].pack('c') | |
op = proc{|o, a, b, c| | |
o | (a << 6) | (b << (6 + 8 + 9)) | (c << (6 + 8)) | |
} | |
op_jmp = 23 | |
skip = '' | |
if num_skip > 0 | |
raise if num_skip <= 1 | |
buf = [0] * 24 | |
# buf[4] = 6 # for jvm | |
# # buf[5] = 20 # for node | |
# skip += buf.pack("c*") | |
skip_jmp = op[op_jmp, 0, 256, num_skip - 1 - 4] | |
STDERR.puts "[+] lua skip_jmp %0x" % skip_jmp | |
skip += [skip_jmp].pack("L") | |
skip += ([0] * (num_skip-4)).pack("L*") | |
end | |
bc2 = skip | |
todos = [] | |
(bc.size / 4).times do |i| | |
todos << bc[i * 4, 4] | |
end | |
raise if todos.size != 12 | |
todos[0] = [todos[0].unpack1("L") + (1 << 14)].pack("L") | |
todos[1] = [todos[1].unpack1("L") + (1 << 14)].pack("L") | |
todos[2] = [todos[2].unpack1("L") + (1 << 14)].pack("L") | |
todos[5] = [todos[5].unpack1("L") + (1 << 23)].pack("L") | |
todos[6] = [todos[6].unpack1("L") + (1 << 14)].pack("L") | |
todos[7] = [todos[7].unpack1("L") + (1 << 14)].pack("L") | |
todos[8] = [todos[8].unpack1("L") + (1 << 14)].pack("L") | |
jvm_start = 472 | |
lua_start = 475 | |
num_op = 12 | |
lp = lua_start | |
while !todos.empty? | |
m = (lp - jvm_start) % 9 | |
if m == 0 | |
bc2 += [0].pack("L") | |
elsif m >= 1 && m <= 4 | |
bc2 += todos.shift | |
elsif m == 5 | |
bc2 += [op[op_jmp, 0, 256, 0]].pack("L") | |
else | |
bc2 += [0xff].pack("L") | |
end | |
#puts "#{lp} #{ans}" | |
lp += 4 | |
end | |
# bc2 += [op[op_jmp, 0, 256, 400-1-4]].pack("L") | |
# bc2 += ([0] * 400).pack("L*") | |
# bc2 += bc | |
while bc2.size / 4 < 774 - 512 | |
bc2 += ([0] * (1)).pack("L*") | |
end | |
# #bc2 += ([0] * (1+256+206 - 1 - 400 + 4)).pack("L*") | |
bc = bc2 | |
STDERR.puts "[lua] new bc_size=#{bc.size / 4} rest=#{rest.size}" | |
out = preamble + out[preamble_size, code_off] + [bc.size / 4].pack("L") + bc | |
out += [const_size + 1].pack("L") | |
out += [4].pack("c") | |
strsz = 0x600 | |
out += [strsz].pack("Q") | |
out += ([0] * strsz).pack("c*") | |
out += rest | |
write_file("out.luac", out) | |
#check_lua("out.luac") | |
return out | |
end | |
def run_ruby(filename) | |
ruby_run_source = %Q( | |
RubyVM::InstructionSequence.load_from_binary(File.open('#{filename}', 'r').readlines.join('')).eval | |
) | |
write_file("run_bc.rb", ruby_run_source) | |
pipe = IO.popen("#{$ruby} run_bc.rb", "r+") | |
return pipe.read | |
end | |
def check_ruby(filename) | |
out = run_ruby(filename) | |
if !out.include?($flag) | |
raise "No flag in #{out}" | |
end | |
end | |
def forge_ruby(offset) | |
preamble_size = 12 | |
ruby_compile_source = %Q( | |
bc = RubyVM::InstructionSequence.compile(%Q(p File.read('flag'))) | |
File.open('read_flag.rb.bc', 'w').write(bc.to_binary) | |
) | |
write_file('compile_read_flag.rb', ruby_compile_source) | |
if !system("#{$ruby} compile_read_flag.rb") | |
raise "ruby failed" | |
end | |
check_ruby("read_flag.rb.bc") | |
c = read_file("read_flag.rb.bc") | |
bc_size = c[preamble_size, 4].unpack1("L") | |
STDERR.puts "[+] ruby bc_size=#{bc_size}" | |
# Fails if we make bc_size larger! | |
c[preamble_size, 4] = [bc_size].pack("L") | |
out = c | |
write_file("out.rb.bc", out) | |
check_ruby("out.rb.bc") | |
return out | |
end | |
def run_node(filename) | |
pipe = IO.popen("#{$bytenode} #{filename}", "r+") | |
return pipe.read | |
end | |
def check_node(filename) | |
out = run_node(filename) | |
if !out.include?($flag) | |
raise "No flag in #{out}" | |
end | |
end | |
def forge_node(offset) | |
node_source = %Q(console.log(require("fs").readFileSync("flag")+0)) | |
write_file('read_flag.js', node_source) | |
if !system("#{$bytenode} -c read_flag.js") | |
raise "bytenode failed" | |
end | |
check_node("read_flag.jsc") | |
c = read_file('read_flag.jsc') | |
# The check for magic_number was patched. | |
c[0, 4] = [0].pack("L") | |
# version_hash | |
c[4, 4] = [0].pack("L") | |
# source_hash | |
c[8, 4] = [0].pack("L") | |
# cpu_features | |
c[12, 4] = [0].pack("L") | |
# flags_hash | |
c[16, 4] = [0].pack("L") | |
extra_resv = 0 | |
# num reservations offset | |
c[20, 4] = [5 + extra_resv].pack("L") | |
# code stub key | |
c[24, 4] = [0].pack("L") | |
# payload length | |
c[28, 4] = [0].pack("L") | |
# checksum1 | |
c[32, 4] = [0].pack("L") | |
# checksum2 | |
c[36, 4] = [0].pack("L") | |
out = c[0, 60] | |
out += ([0] * extra_resv).pack("L*") | |
out += c[60..-1] | |
#out += ([0] * 199).pack("L*") | |
write_file("out.jsc", out) | |
check_node("out.jsc") | |
return out | |
end | |
def run_python3(filename) | |
pipe = IO.popen("python3 #{filename}", "r+") | |
return pipe.read | |
end | |
def check_python3(filename) | |
out = run_python3(filename) | |
if !out.include?($flag) | |
raise "No flag in #{out}" | |
end | |
end | |
def forge_python3(offset) | |
preamble_size = 4 | |
python_source = %Q(print(open('flag').read())) | |
write_file('read_flag.py', python_source) | |
if !system("python3 -m compileall read_flag.py") | |
raise "python failed" | |
end | |
check_python3("__pycache__/read_flag.cpython-36.pyc") | |
c = read_file("__pycache__/read_flag.cpython-36.pyc") | |
$python3_preamble = preamble = c[0, preamble_size] | |
# Arbitrary timestamp and size. | |
c[4, 4] = [0].pack("L") | |
c[8, 4] = [0].pack("L") | |
# argcount | |
c[12 + 1, 4] = [0].pack("L") | |
# kwonlyargcount | |
c[16 + 1, 4] = [0].pack("L") | |
# nlocals | |
c[20 + 1, 4] = [0].pack("L") | |
# stacksize | |
c[24 + 1, 4] = [0].pack("L") | |
# flags | |
c[28 + 1, 4] = [0].pack("L") | |
out = c | |
write_file("out.pyc", out) | |
check_python3("out.pyc") | |
return out | |
end | |
def run_python2(filename) | |
pipe = IO.popen("python2 #{filename}", "r+") | |
return pipe.read | |
end | |
def check_python2(filename) | |
out = run_python2(filename) | |
if !out.include?($flag) | |
raise "No flag in #{out}" | |
end | |
end | |
def forge_python2(offset) | |
preamble_size = 4 | |
#python_source = %Q(print(open('flag').read())) | |
python_source = %Q(# coding=latin-1 | |
import os | |
os.write(1, open('flag').read()) | |
os._exit(0) | |
) | |
write_file('read_flag.py', python_source) | |
if !system("python2 -m compileall read_flag.py") | |
raise "python failed" | |
end | |
check_python2("read_flag.pyc") | |
c = read_file("read_flag.pyc") | |
$python2_preamble = preamble = c[0, preamble_size] | |
timestamp = c[4, 4].unpack("L*")[0] | |
argcount, nlocals, stacksize, flags = c[8 + 1, 4 * 4].unpack("L*") | |
puts "[+] python2 timestamp=#{timestamp} argcount=#{argcount} nlocals=#{nlocals} stacksize=#{stacksize} flags=#{flags}" | |
# Arbitrary timestamp. | |
c[4, 4] = [0].pack("L") | |
# argcount | |
c[8 + 1, 4] = [0].pack("L") | |
# nlocals | |
c[12 + 1, 4] = [0].pack("L") | |
# stacksize | |
c[16 + 1, 4] = [0].pack("L") | |
# flags | |
c[20 + 1, 4] = [0].pack("L") | |
bc_size = c[26, 4].unpack1("L") | |
STDERR.puts "[+] python2 bc_size=#{bc_size}" | |
bc = c[30, bc_size] | |
jmp_high = 6 | |
jmp_low = 255 | |
bc = [0x6e, jmp_low, jmp_high].pack("c*") + ([0] * (jmp_high * 256 + jmp_low)).pack("c*") + bc | |
bc += ([0] * 1500).pack("c*") | |
out = c[0, 26] | |
out += [bc.size].pack("L") | |
out += bc | |
out += c[26 + 4 + bc_size..-1] | |
write_file("out.pyc", out) | |
check_python2("out.pyc") | |
write_file("out.pyc.bc", out[4..-1]) | |
return out | |
end | |
def gen_elisp_read_flag | |
bconst = 0xc0 | |
bcall = 0x20 | |
bcall1 = 0x21 | |
bcall4 = 0x24 | |
breturn = 0x87 | |
bdiscard = 0x88 | |
elc = [] | |
elc << bconst + 26 + 1 # insert-file-contents | |
elc << bconst + 26 # string | |
elc << bconst + ?f.ord - ?a.ord | |
elc << bconst + ?l.ord - ?a.ord | |
elc << bconst + ?a.ord - ?a.ord | |
elc << bconst + ?g.ord - ?a.ord | |
elc << bcall4 | |
elc << bcall1 | |
elc << bdiscard | |
elc << bconst + 26 + 2 # print | |
elc << bconst + 26 + 3 # buffer-string | |
elc << bcall | |
elc << bcall1 | |
elc << breturn | |
elc.pack("c*") | |
end | |
def run_elisp(filename) | |
system("cp #{filename} bytecode") | |
pipe = IO.popen("service/src/platforms/elisp/run", "r+") | |
return pipe.read | |
end | |
def check_elisp(filename) | |
# X crashes for some reason :( | |
return | |
out = run_elisp(filename) | |
if !out.include?($flag) | |
raise "No flag in #{out}" | |
end | |
end | |
def forge_elisp(offset) | |
out = '' | |
# To keep Java's constant pool small. | |
out += [1].pack("c*") | |
# bgoto | |
out += [0x82, offset % 256, offset / 256].pack("c*") | |
out += ([0] * (offset - out.size)).pack("c*") | |
out += gen_elisp_read_flag | |
write_file("out.elc", out) | |
check_elisp("out.elc") | |
return out | |
end | |
def run_jvm(filename) | |
if filename != "Bytecode.class" | |
system("cp #{filename} Bytecode.class") | |
end | |
pipe = IO.popen("java Bytecode", "r+") | |
return pipe.read | |
end | |
def check_jvm(filename) | |
out = run_jvm(filename) | |
if !out.include?($flag) | |
raise "No flag in #{out}" | |
end | |
end | |
def forge_jvm(offset) | |
preamble_size = 8 | |
java_source = %Q( | |
import java.io.*; | |
public class Bytecode { | |
public static void main(String[] args) throws Exception { | |
FileReader r = new FileReader("flag"); | |
BufferedReader b = new BufferedReader(r); | |
System.out.println(b.readLine()); | |
} | |
} | |
) | |
write_file('Bytecode.java', java_source) | |
if !system("javac -g:none Bytecode.java") | |
raise "javac failed" | |
end | |
check_jvm("Bytecode.class") | |
c = read_file("Bytecode.class") | |
write_file("read_flag.class", c) | |
$jvm_preamble = preamble = c[0, preamble_size] | |
body = c[preamble_size..-1] | |
bc = BinaryConsumer.new(body) | |
num_consts = bc.read(2).unpack1("S>") | |
STDERR.puts "[+] java num_consts=#{num_consts}" | |
const_tag_float = 4 | |
const_tag_double = 6 | |
const_tag_string = 8 | |
const_tag_name_and_type = 12 | |
consts = [] | |
dummy_str_idx = 44 | |
consts << [const_tag_double] | |
consts << [const_tag_double] | |
consts << [const_tag_double] | |
consts << [const_tag_double] | |
consts << [const_tag_double] | |
consts << [const_tag_float] | |
consts << [const_tag_string, dummy_str_idx] | |
num_double = consts.count([const_tag_double]) | |
idx_diff = consts.size + num_double | |
(num_consts - 1).times do | |
tag = bc.read(1).unpack1("c") | |
if tag == 1 | |
len = bc.read(2).unpack1("s>") | |
str = bc.read(len) | |
STDERR.puts "[-] java UTF8 v=#{str}" | |
consts << [tag, str] | |
elsif tag == 7 | |
idx = bc.read(2).unpack1("s>") + idx_diff | |
STDERR.puts "[-] java Classs idx=#{idx}" | |
consts << [tag, idx] | |
elsif tag == 8 | |
idx = bc.read(2).unpack1("s>") + idx_diff | |
STDERR.puts "[-] java String idx=#{idx}" | |
consts << [tag, idx] | |
elsif tag == 9 | |
ci = bc.read(2).unpack1("s>") + idx_diff | |
nti = bc.read(2).unpack1("s>") + idx_diff | |
STDERR.puts "[-] java Fieldref ci=#{ci} nti=#{nti}" | |
consts << [tag, ci, nti] | |
elsif tag == 10 | |
ci = bc.read(2).unpack1("s>") + idx_diff | |
nti = bc.read(2).unpack1("s>") + idx_diff | |
STDERR.puts "[-] java Methodref ci=#{ci} nti=#{nti}" | |
consts << [tag, ci, nti] | |
elsif tag == 12 | |
ni = bc.read(2).unpack1("s>") + idx_diff | |
di = bc.read(2).unpack1("s>") + idx_diff | |
STDERR.puts "[-] java NameAndType ni=#{ni} di=#{di}" | |
consts << [tag, ni, di] | |
else | |
raise "Unknown tag #{tag}" | |
end | |
end | |
0.times do | |
consts << [const_tag_string, dummy_str_idx] | |
end | |
nc = 13 | |
nc.times { | |
consts << [const_tag_double] | |
} | |
num_double += nc | |
157.times do | |
consts << [const_tag_string, dummy_str_idx] | |
end | |
consts << [const_tag_double] | |
num_double += 1 | |
consts << [const_tag_double] | |
num_double += 1 | |
while consts.size + num_double < 0x181 | |
consts << [const_tag_string, dummy_str_idx] | |
end | |
access_off = bc.offset | |
access_flags = bc.read(2).unpack1("s>") | |
this_flags = bc.read(2).unpack1("s>") + idx_diff | |
super_flags = bc.read(2).unpack1("s>") + idx_diff | |
STDERR.puts "[-] java access_flags=#{access_flags} this_flags=#{this_flags} super_flags=#{super_flags}" | |
interfaces_count = bc.read(2).unpack1("s>") | |
STDERR.puts "[-] java interfaces_count=#{interfaces_count}" | |
interfaces = [] | |
interfaces_count.times do | |
interfaces << bc.read(2).unpack1("s>") | |
end | |
raise if interfaces_count > 0 | |
fields_count = bc.read(2).unpack1("s>") | |
STDERR.puts "[-] java fields_count=#{fields_count}" | |
raise if fields_count > 0 | |
method_off = bc.offset | |
methods_count = bc.read(2).unpack1("s>") | |
STDERR.puts "[-] java methods_count=#{methods_count}" | |
methods = [] | |
methods_count.times do | |
af = bc.read(2).unpack1("s>") | |
ni = bc.read(2).unpack1("s>") + idx_diff | |
di = bc.read(2).unpack1("s>") + idx_diff | |
ac = bc.read(2).unpack1("s>") | |
STDERR.puts "[-] java method access_flags=#{af} name_index=#{ni} descriptor_index=#{di} attributes_count=#{ac}" | |
attrs = [] | |
ac.times do | |
ani = bc.read(2).unpack1("s>") + idx_diff | |
attr_len = bc.read(4).unpack1("l>") | |
attr = bc.read(attr_len) | |
STDERR.puts "[-] java attribute_name_index=#{ani} attr_len=#{attr_len}" | |
attrs << [ani, attr] | |
end | |
methods << [af, ni, di, attrs] | |
end | |
fixup = proc{|mi, ai, off| | |
fc = methods[mi][3][ai][1] | |
fc[off, 2] = [fc[off, 2].unpack1("s>") + idx_diff].pack("s>") | |
} | |
fixup[0, 0, 9 + 1] | |
fixup[1, 0, 9 + 0] | |
fixup[1, 0, 9 + 3] | |
fixup[1, 0, 9 + 6] | |
fixup[1, 0, 9 + 10] | |
fixup[1, 0, 9 + 15] | |
fixup[1, 0, 9 + 19] | |
fixup[1, 0, 9 + 23] | |
fixup[1, 0, 9 + 26] | |
fixup[1, 1, 2] | |
attr_off = bc.offset | |
attributes_count = bc.read(2).unpack1("s>") | |
STDERR.puts "[-] java attributes_count=#{attributes_count}" | |
attributes = [] | |
attributes_count.times do | |
ani = bc.read(2).unpack1("s>") | |
attr_len = bc.read(4).unpack1("l>") | |
attr = bc.read(attr_len) | |
STDERR.puts "[-] java attribute_name_index=#{ani} attr_len=#{attr_len}" | |
end | |
raise if preamble_size + bc.offset != c.size | |
out = preamble | |
# out += body[0, access_off] | |
out += [consts.size + 1 + num_double].pack("s>") | |
consts.each do |tag, *ci| | |
out += [tag].pack("c") | |
if tag == 1 | |
str = ci[0] | |
out += [str.size].pack("s>") | |
out += str | |
elsif tag == 4 | |
out += [0].pack("l>") | |
elsif tag == 5 || tag == 6 | |
out += [0].pack("q>") | |
elsif tag == 7 | |
out += ci.pack("s>*") | |
elsif tag == 8 | |
out += ci.pack("s>*") | |
elsif tag == 9 | |
out += ci.pack("s>*") | |
elsif tag == 10 | |
out += ci.pack("s>*") | |
elsif tag == 12 | |
out += ci.pack("s>*") | |
else | |
raise "Unknown tag #{tag}" | |
end | |
end | |
out += [access_flags, this_flags, super_flags].pack("s>*") | |
out += [interfaces_count, fields_count].pack("s>*") | |
out += [methods.size].pack("s>") | |
methods.each do |af, ni, di, attrs| | |
out += [af, ni, di, attrs.size].pack("s>*") | |
attrs.each do |ani, attr| | |
out += [ani].pack("s>") | |
out += [attr.size].pack("l>") | |
out += attr | |
end | |
end | |
target_size = 4000 | |
dummy_attr_len = target_size - out.size - 2 - 2 - 4 | |
out += [1].pack("s>") | |
out += [dummy_str_idx].pack("s>") | |
out += [dummy_attr_len].pack("l>") | |
out += ([0] * dummy_attr_len).pack("c*") | |
write_file("out.class", out) | |
check_jvm("out.class") | |
return out | |
end | |
def polyglot(bcs) | |
out = '' | |
5000.times do |i| | |
o = 0 | |
winner = nil | |
code_exist = false | |
bcs.each do |name, bc, preamble| | |
c = bc[preamble.size + i] | |
next if !c | |
c = c.ord | |
raise "too large for 4k limit #{name}" if i > 4000 | |
code_exist = true | |
if c != o | |
if o == 0 | |
STDERR.puts "[-] use #{name} for i=#{i} v=%02x" % c | |
o = c | |
winner = name | |
elsif c != 0 | |
raise "conflict at #{i} #{name}(#{c}) vs #{winner}(#{o})" | |
end | |
end | |
end | |
break if !code_exist | |
out << [o].pack("c") | |
end | |
write_file('polyglot.bc', out) | |
exts = { | |
'ocaml' => 'cmo', | |
'lua' => 'luac', | |
'python3' => 'pyc', | |
'python2' => 'pyc', | |
'java' => 'class', | |
'node' => 'jsc', | |
} | |
bcs.each do |name, bc, preamble| | |
STDERR.puts "[+] #{name} testing" | |
checker = method('check_' + name) | |
filename = "polyglot_#{name}.#{exts[name]}" | |
write_file(filename, preamble + out) | |
checker.call(filename) | |
STDERR.puts "[+] #{name} OK!" | |
end | |
return out | |
end | |
# Restriction: first 4B must be cu offset in BE. | |
ocaml_bc = forge_ml(0xc00) | |
# Restriction: first 8B is non-negative in LE. | |
# Next 3B are | |
# numparams = 0 | |
# is_vararg = 1 | |
# maxstacksize = 3 | |
# Note the skip jmp size is sensitive for python2's stack. | |
lua_bc = forge_lua(118) # for py2 | |
#lua_bc = forge_lua(800 - 40 - 256) # for jvm | |
#lua_bc = forge_lua(800 - 40) | |
# Restriction: first 4B is bytecode size in LE. | |
ruby_bc = forge_ruby(4000) | |
# Restriction: first 8B is abitrary. | |
# TYPE_CODE = e3 [1B] | |
# argc = 0 | |
# kwonlyargcount = 0 | |
# nlocals = 0 | |
# stacksize = 0 | |
# flags = 0 | |
# TYPE_STRING = 74 [1B] | |
py3_bc = forge_python3(4000) | |
# Restriction: first 4B is abitrary. | |
# TYPE_CODE = e3 [1B] | |
# argc = 0 | |
# nlocals = 0 | |
# stacksize = 0 | |
# flags = 0 | |
# TYPE_STRING = 74 [1B] | |
py2_bc = forge_python2(4000) | |
jvm_bc = forge_jvm(2000) | |
# Restriction: first 4B is bd 03 de c0. | |
node_bc = forge_node(2000) | |
# Restriction: first 4B is jmp to the actual bytecode. | |
elisp_bc = forge_elisp(3900-56+2) | |
bcs = [ | |
#['ocaml', ocaml_bc, $ml_preamble], | |
['elisp', elisp_bc, ''], | |
#['node', node_bc, $node_preamble], | |
['jvm', jvm_bc, $jvm_preamble], | |
['python2', py2_bc, $python2_preamble], | |
['lua', lua_bc, $lua_preamble], | |
] | |
polyglot(bcs) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment