Continuing with my Binary Golf adventure for year 5, I've decided to try and create the smallest Java class that downloads and presents the data downloaded from the Binary Golf website.
Some conclusions from my Linux shellcode blogpost:
- Running
curlis fair game and saves a lot of file size. - Short URLs are fair game, and the shortest I found was
7f.uk.
With that, let's begin!
The first thing I did was just compile a simple java class with no debugging information:
public class a {
public static void main(String[] x) throws Exception {
ProcessBuilder p = new ProcessBuilder("curl", "-L", "7f.uk");
p.inheritIO();
p.start();
}
}Remarks:
- Note the class name is
a(as the class name is preserved in the Java class). - We need to declare
Exceptionmight be thrown (due top.start(). - I had to call
p.inheritIO()for the newly created process to use the current process'sstdout. - Normally you're supposed to wait until the process finishes, but I found it okay to just run it - the Java process ends but the new process still finishes a bit later, which is okay according to the rules of the game I guess.
Compiling with javac -g:none a.java produces a class of 446 bytes:
00000000│cafe babe 0000 003d│0023 0a00 0200 0307│.......=.#......
00000010│0004 0c00 0500 0601│0010 6a61 7661 2f6c│..........java/l
00000020│616e 672f 4f62 6a65│6374 0100 063c 696e│ang/Object...<in
00000030│6974 3e01 0003 2829│5607 0008 0100 186a│it>...()V......j
00000040│6176 612f 6c61 6e67│2f50 726f 6365 7373│ava/lang/Process
00000050│4275 696c 6465 7207│000a 0100 106a 6176│Builder......jav
00000060│612f 6c61 6e67 2f53│7472 696e 6708 000c│a/lang/String...
00000070│0100 0463 7572 6c08│000e 0100 022d 4c08│...curl......-L.
00000080│0010 0100 0537 662e│756b 0a00 0700 120c│.....7f.uk......
00000090│0005 0013 0100 1628│5b4c 6a61 7661 2f6c│.......([Ljava/l
000000a0│616e 672f 5374 7269│6e67 3b29 560a 0007│ang/String;)V...
000000b0│0015 0c00 1600 1701│0009 696e 6865 7269│..........inheri
000000c0│7449 4f01 001c 2829│4c6a 6176 612f 6c61│tIO...()Ljava/la
000000d0│6e67 2f50 726f 6365│7373 4275 696c 6465│ng/ProcessBuilde
000000e0│723b 0a00 0700 190c│001a 001b 0100 0573│r;.............s
000000f0│7461 7274 0100 1528│294c 6a61 7661 2f6c│tart...()Ljava/l
00000100│616e 672f 5072 6f63│6573 733b 0700 1d01│ang/Process;....
00000110│0001 6101 0004 436f│6465 0100 046d 6169│..a...Code...mai
00000120│6e01 000a 4578 6365│7074 696f 6e73 0700│n...Exceptions..
00000130│2201 0013 6a61 7661│2f6c 616e 672f 4578│"...java/lang/Ex
00000140│6365 7074 696f 6e00│2100 1c00 0200 0000│ception.!.......
00000150│0000 0200 0100 0500│0600 0100 1e00 0000│................
00000160│1100 0100 0100 0000│052a b700 01b1 0000│.........*......
00000170│0000 0009 001f 0013│0002 001e 0000 0032│...............2
00000180│0006 0002 0000 0026│bb00 0759 06bd 0009│.......&...Y....
00000190│5903 120b 5359 0412│0d53 5905 120f 53b7│Y...SY...SY...S.
000001a0│0011 4c2b b600 1457│2bb6 0018 57b1 0000│..L+...W+...W...
000001b0│0000 0020 0000 0004│0001 0021 0000 │... .......!..
Now is the time to start trimming some unnecessary things, including some strings that might be easily trimmed (e.g. java/lang/Exception). This kind of forces me to dive into class file format!
- I tried using a URLClassLoader to load further code from some short-form URL I register, but the size turned up to be too big (
457bytes to be exact). - I tried inheriting from
ProcessBuilderto not have a direct reference toObjectas a superclass, but it didn't work due toProcessBuilderbeingfinal. - I thought of incorporating
nativecode, but could not find easy ways inlibc.so.6to make it appear as if we have a JNI method that'd not crash the process. - At a certain point I thought that the
-Lflag should be omitted at the price of using the entire URL for the Binary Golf download target, but it turns out the short URL with the-Lflag actually save 5 bytes, so in total you should still use the short URL even at the price of one more constant pool entry. - The documentation states you could leave a value of
0(which is kind ofnull) for the index that represents the descriptor of the superclass instead of referring tojava.lang.Object. That could save a lot of room, but unfortunately the JVM verifier validates this and refuses to run the code. - Initially I thought using
Runtime.getRuntime.execis not a good approach, due to not being able to easily divert the standard output without bloating the code. It took me a while to figure out a neat trick, keep reading! - I tried just not returning from my code (i.e. removing the
returnbytecode instruction), but the verifier looks for that and refuses to load your code.
The Java class format is quite simple and very well documented here.
Without going into too many details, the format is quite simple:
- Everything is Big-Endian (yikes!)
- A simple header exists that has a magic value, as well as the major and minor versions.
- Then, there's a constant pool in the form of number of entries and then the entries themselves. Each entry has a tag (type) and its data, which is determined by the type. There are 14 supported types, and they aren't terrible to parse.
- Then we have 3 other simple fields: the class's access flags, followed by an index that should point to a descriptor of the class (in the constant pool) and an index that points to the superclass.
- Finally, we have interfaces, fields, methods and attributes. All of them are simple arrays, and most of them use indices that point to the constant pool.
It took me around an hour to code my own Java class parser and I share it in this repository (see java_fmt.py).
Let us run it and parse the header:
$ ./java_fmt.py ./a.class
MENU
FILE: a.class
[H]eader
[C]onstant pool (34)
[D]escriptor for the class
[Interfaces (0)
[F]ields (0)
[M]ethods (2)
[A]ttributes (0)
[Q]uit
> H
HEADER
version_minor = 0
major = 61
access_flags = 33
this_class_index (28) -->
tag = CONSTANT_Class
name_index (29) -->
tag = CONSTANT_Utf8
data = a
super_class_index (2) -->
tag = CONSTANT_Class
name_index (4) -->
tag = CONSTANT_Utf8
data = java/lang/ObjectThat's quite interesting! We see how the constant pool is used, for instance:
- The
this_class_indexmember has a value of28, which points to the27th member of the constant pool. - The
27th item in the constant pool has a tag of typeCONSTANT_Class, which makes sense since this should be a class descriptor. - The class descriptor has a
name_indexwith the value of29, thus pointing to the28th member of the constant pool. - The
28th item in the constant pool is of typeCONSTANT_Utf8and has the dataain it. UTF-8 strings are simply Pascal-strings (lengthfollowed bydatawith no NUL terminators). - Similarly, the
super_class_indexis2and points to item1in the constant pool, which is a class descriptor that has a name index that eventually points to a UTF-8 string ofjava/lang/Object.
Already several ideas come to mind:
- How will the JVM treat a descriptor that points to a tag that does not have the type
CONSTANT_Class? If ignored, we might reuse entries in the constant pool. - Can we do an out-of-bounds in one of the indices?
- Can we call our class
curl, thus reusing the (expected)curlstring in the constant pool?
Let's keep all those ideas in our minds, while we print out our methods:
METHODS
access_flags = 1
name_index (5) -->
tag = CONSTANT_Utf8
data = <init>
descriptor_index (6) -->
tag = CONSTANT_Utf8
data = ()V
attributes: [
attribute_name_index (30) -->
tag = CONSTANT_Utf8
data = Code
data = 00 01 00 01 00 00 00 05 2a b7 00 01 b1 00 00 00 00
]
access_flags = 9
name_index (31) -->
tag = CONSTANT_Utf8
data = main
descriptor_index (19) -->
tag = CONSTANT_Utf8
data = ([Ljava/lang/String;)V
attributes: [
attribute_name_index (30) -->
tag = CONSTANT_Utf8
data = Code
data = 00 06 00 02 00 00 00 26 bb 00 07 59 06 bd 00 09 59 03 12 0b 53 59 04 12 0d 53 59 05 12 0f 53 b7 00 11 4c 2b b6 00 14 57 2b b6 00 18 57 b1 00 00 00 00
attribute_name_index (32) -->
tag = CONSTANT_Utf8
data = Exceptions
data = 00 01 00 21
]Interestingly, we have 2 methods!
- One method is called
<init>and gets no argument and returnsvoid(this is the()Vpart). That is a constructor to our class, with very minimal code. - The other method is our
mainmethod that gets an array of strings ([Ljava/lang/String;) and returns nothing. - Our
<init>method has one attribute calledCodeand then the bytecode (which I didn't parse here). - Our
mainmethod has a similarCodeattribute, but also has another attribute calledExceptions, which probably declares the fact our method might throw exceptions.
New ideas come to mind:
- Is the
<init>method necessary? Also, what does its code do? Can we trim it? - Can we simply omit the
Exceptionsattribute frommain? - Will our JVM be able to run
mainthat does not accept any arguments? - Can we move our entire logic to
<init>?
For that, we need to run some experiments.
Some of the easiest experiments do not involve any serious hacking:
- We can indeed name our class
curl, and it saves4bytes in total, asjavacrepurposes thecurlstring constant and uses it twice (once for the class name, once for the string used in the code). The total size reduces to442bytes. - We cannot get rid of
mainmethod, as the JVM complains if it doesn't find the exactmainmethod that gets an array ofStrings.
Now, let's get to the harder questions:
You can get rid of the <init> method, but it'd bite you back later. Removing it completely completely results in this:
Error: Unable to initialize main class curl
Caused by: java.lang.VerifyError: Bad invokespecial instruction: current class isn't assignable to reference class.
Exception Details:
Location:
curl.main([Ljava/lang/String;)V @23: invokespecial
Reason:
Error exists in the bytecode
Bytecode:
0000000: bb00 0759 06bd 0009 5903 120b 5359 0412
0000010: 0d53 5905 120f 53b7 0011 4c2b b600 1457
0000020: 2bb6 0018 57b1
The invoke-special instruction is 0xb7, and it means to run an instance method, which is a bit different than running virtual methods (which is the default behavior in Java).
One other idea we could do is move everything to a new method called <clinit>, which is the static constructor, that runs when the class gets loaded. This can even be done without any binary patching:
public class curl {
static {
ProcessBuilder p = new ProcessBuilder("curl", "-L", "7f.uk");
p.inheritIO();
try {
p.start();
}
catch (Exception e) {}
}
}Unfortunately, this doesn't run, as the JVM refuses to run without a proper main method.
Adding a main method that doesn't do anything creates a larger binary, as we now have 3 methods: <clinit>, <init> an main, which results in a larger constant pool.
As for patching <init>, I didn't talk too much about the bytecode itself, but there's a good reference here. I tried patching its first instruction to be 0xb1 (which is return-void) but the JVM complains about not initializing the superclass:
Error: Unable to initialize main class curl
Caused by: java.lang.VerifyError: Constructor must call super() or this() before return
Exception Details:
Location:
curl.<init>()V @0: return
Reason:
Error exists in the bytecode
Bytecode:
0000000: b1b7 0001 b1
Indeed it looks like the JVM verifier validates the constructor initializes the superclass. Since the total code length of <init> is 5 bytes, it cannot be improved further.
- I've tried checking if the
tagis really used or we can abuse a type confusion. The JVM I have strongly checks thetag. - Out-of-bounds in the constant pool is a no-go, resulting in verifier failures (if used statically) or crashes (if used in a
Codeattribute).
One more thing left to do: our main method has an extra attribute called Exceptions and has 4 bytes.
The format of the Exceptions data is simply the number of the exceptions (2-bytes) followed by exception reference numbers (2 bytes each).
Our bytes are 00 01 00 20, so we have 1 exception referencing index 0x20 (32), and indeed, using my tool we see:
CONSTANT POOL (32)
tag = CONSTANT_Class
name_index (33) -->
tag = CONSTANT_Utf8
data = java/lang/Exception
Additionally, the name Exceptions itself is referenced by index 31. So, our plan is:
- Removing entry
31from the constant pool (the UTF-8 constant that saysExceptions). - Removing entry
32from the constant pool (the class descriptor forJava/lang/Exception). - Removing entry
33from the constant pool (the UTF-8 string forJava/lang/Exception). - Removing the
2nd attribute from the2nd method.
The effect is that it'd be as if we didn't declare main to throw Exception, which is fine for our execution.
I've done that work manually with some binary editing, and ended up with the following 390 bytes curl.class file... And indeed:
jbo@McJbo % java curl
jbo@McJbo % Another #BGGP5 download!! @binarygolf https://binary.golfWhen running with my tool, it looks like this:
METHODS
access_flags = 1
name_index (5) -->
tag = CONSTANT_Utf8
data = <init>
descriptor_index (6) -->
tag = CONSTANT_Utf8
data = ()V
attributes: [
attribute_name_index (29) -->
tag = CONSTANT_Utf8
data = Code
data = 00 01 00 01 00 00 00 05 2a b7 00 01 b1 00 00 00 00
]
access_flags = 9
name_index (30) -->
tag = CONSTANT_Utf8
data = main
descriptor_index (19) -->
tag = CONSTANT_Utf8
data = ([Ljava/lang/String;)V
attributes: [
attribute_name_index (29) -->
tag = CONSTANT_Utf8
data = Code
data = 00 06 00 01 00 00 00 22 bb 00 07 59 06 bd 00 09 59 03 12 0b 53 59 04 12 0d 53 59 05 12 0f 53 b7 00 11 b6 00 14 b6 00 18 57 b1 00 00 00 00
]
Surprisingly, because main is a static method, I discovered you could have an abstract class that'd still run!
public abstract class curl {
public static void main(String[] x) throws Exception {
(new ProcessBuilder("curl", "-L", "7f.uk")).inheritIO().start();
}
}This runs well, but still generates an <init> method... But because the class is abstract, it should never be called... Can we omit it?
Apparently, we can, saving the <init> method and all its references in the constant pool (there are 3 of those), as well as removing the Exception handling. After patching, I was able to reduce the class to a size of 343 bytes only!
00000000│cafe babe 0000 003d│001c 0100 046d 6169│.......=.....mai
00000010│6e07 0004 0100 0443│6f64 6501 0010 6a61│n......Code...ja
00000020│7661 2f6c 616e 672f│4f62 6a65 6374 0100│va/lang/Object..
00000030│063c 696e 6974 3e07│000c 0700 0801 0018│.<init>.........
00000040│6a61 7661 2f6c 616e│672f 5072 6f63 6573│java/lang/Proces
00000050│7342 7569 6c64 6572│0700 0a01 0010 6a61│sBuilder......ja
00000060│7661 2f6c 616e 672f│5374 7269 6e67 0800│va/lang/String..
00000070│0c01 0004 6375 726c│0800 0e01 0002 2d4c│....curl......-L
00000080│0800 1001 0005 3766│2e75 6b0a 0007 0012│......7f.uk.....
00000090│0c00 0500 1301 0016│285b 4c6a 6176 612f│........([Ljava/
000000a0│6c61 6e67 2f53 7472│696e 673b 2956 0a00│lang/String;)V..
000000b0│0700 150c 0016 0017│0100 0969 6e68 6572│...........inher
000000c0│6974 494f 0100 1c28│294c 6a61 7661 2f6c│itIO...()Ljava/l
000000d0│616e 672f 5072 6f63│6573 7342 7569 6c64│ang/ProcessBuild
000000e0│6572 3b0a 0007 0019│0c00 1a00 1b01 0005│er;.............
000000f0│7374 6172 7401 0015│2829 4c6a 6176 612f│start...()Ljava/
00000100│6c61 6e67 2f50 726f│6365 7373 3b04 2100│lang/Process;.!.
00000110│0600 0200 0000 0000│0100 0900 0100 1300│................
00000120│0100 0300 0000 2e00│0600 0100 0000 22bb│..............".
00000130│0007 5906 bd00 0959│0312 0b53 5904 120d│..Y....Y...SY...
00000140│5359 0512 0f53 b700│11b6 0014 b600 1857│SY...S.........W
00000150│b100 0000 0000 00 │ │.......
Lastly, our class currently inherits from Object, which takes the string java/lang/Object in the constant pool.
I noticed that isn't referenced by anything besides our superclass descriptor. My first thought was to use a different class descriptor, but:
- While the
ProcessBuilderandStringclasses exist in our constant pool, they are bothfinalso the verifier fails for inheriting from them. - I cannot self-inherit, even though I am an
abstractclass - the verifier fails on these kind of recursive definitions.
I didn't find any other strings that would be usable classes to inherit from, so my goal was to shorten java/lang/Object to a shorter class.
Primitive types or their arrays would be ideal, but they are not available for inheritence, so I ended up checking all builtin classes and find the one with the shortest name: java/io/File and others have a total length of 12 characters.
So, replacing that string constant from java/lang/Object to java/io/File reduces my file size further to 339 bytes:
00000000│cafe babe 0000 003d│001c 0100 046d 6169│.......=.....mai
00000010│6e07 0004 0100 0443│6f64 6501 000c 6a61│n......Code...ja
00000020│7661 2f69 6f2f 4669│6c65 0100 063c 696e│va/io/File...<in
00000030│6974 3e07 000c 0700│0801 0018 6a61 7661│it>.........java
00000040│2f6c 616e 672f 5072│6f63 6573 7342 7569│/lang/ProcessBui
00000050│6c64 6572 0700 0a01│0010 6a61 7661 2f6c│lder......java/l
00000060│616e 672f 5374 7269│6e67 0800 0c01 0004│ang/String......
00000070│6375 726c 0800 0e01│0002 2d4c 0800 1001│curl......-L....
00000080│0005 3766 2e75 6b0a│0007 0012 0c00 0500│..7f.uk.........
00000090│1301 0016 285b 4c6a│6176 612f 6c61 6e67│....([Ljava/lang
000000a0│2f53 7472 696e 673b│2956 0a00 0700 150c│/String;)V......
000000b0│0016 0017 0100 0969│6e68 6572 6974 494f│.......inheritIO
000000c0│0100 1c28 294c 6a61│7661 2f6c 616e 672f│...()Ljava/lang/
000000d0│5072 6f63 6573 7342│7569 6c64 6572 3b0a│ProcessBuilder;.
000000e0│0007 0019 0c00 1a00│1b01 0005 7374 6172│............star
000000f0│7401 0015 2829 4c6a│6176 612f 6c61 6e67│t...()Ljava/lang
00000100│2f50 726f 6365 7373│3b04 2100 0600 0200│/Process;.!.....
00000110│0000 0000 0100 0900│0100 1300 0100 0300│................
00000120│0000 2e00 0600 0100│0000 22bb 0007 5906│.........."...Y.
00000130│bd00 0959 0312 0b53│5904 120d 5359 0512│...Y...SY...SY..
00000140│0f53 b700 11b6 0014│b600 1857 b100 0000│.S.........W....
00000150│0000 00 │ │...
Now we have only one method - our main method. I didn't add the code parsing to my tool, but it's quite easy to do it manually, especially for such short code.
I am not ashamed to say I used the Wikipedia page for the Java bytecode instructions.
The JVM is basically a mix of stack and register machine, so following the instructions is quite easy. Note this is not the stack used by the CPU, it's the JVM stack that is implemented 100% in software.
Anyway, my current code looks like this (annotations were done by me):
bb 00 07 new java/lang/ProcessBuilder (C7)
59 dup
06 iconst_3
bd 00 09 anewarray java/lang/String (C9)
59 dup
03 iconst_0
12 0b ldc "curl" (C11)
53 aastore
59 dup
04 iconst_1
12 0d ldc "-L" C(13)
53 aastore
59 dup
05 iconst_2
12 0f ldc "7f.uk" (C15)
53 aastore
b7 00 11 invokespecial java/lang/ProcessBuilder::<init>([Ljava/lang/String;)V (C17)
b6 00 14 invokevirtual java/lang/ProcessBuilder::inheritIO()Ljava/lang/ProcessBuilder; (C20)
b6 00 18 invokevirtual java/lang/ProcessBuilder::start()Ljava/lang/Process; (C24)
57 pop
b1 returnLet's follow line by line:
- We create a new
ProcessBuilder, which is quite necessary for our execution. Note that this allocates that object (but does not initialize it yet!) and pushes it to the stack. - We duplicate the value in the stack - we now have two references to the same
ProcessBuilderinstance. - We push the constant
3to the stack. - We create a new array of type
String, with a size of3(since3was pushed to the stack). The3value is popped from the stack and the new array is pushed. - We duplicate the last value in the stack, so we now have two references to the
Stringarray in the stack. - We push the value
0to the stack. - We push a reference to the string
curlto the stack. - We store the value
curlat index0to theStringarray - all of those 3 values were popped from the stack and nothing was pushed. That explains thedupin line5- otherwise we'd lose the reference to theStringarray! Our current stack contains two references to the allocatedProcessBuilderfollowed by a reference to theStringarray, which is in the top of the stack. - We duplicate the
Stringarray reference on the stack, similarly to what we did before. - We push the value
1to the stack. - We push a reference to the string
-Lto the stack. - We store the value
-Lat index1to theStringarray, popping all 3 lastly pushed items from the stack, similarly to line 8. - We duplicate the
Stringarray reference on the stack, similarly to what we did before. - We push the value
2to the stack. - We push a reference to the string
7f.ukto the stack. - We store the value
7f.ukat index2to theStringarray, popping all 3 lastly pushed items from the stack, similarly to line 8 and line 12. - We call the
StringBuilder's constructor (<init>) that gets an array ofStrings and the object reference from the stack (unlike what Wikipedia says, it does not put anything back on the stack). - We call
inheritIOvirtual method on theProcessBuilderinstance - the result is pushed back. Since the result ofinheritIOis exactly the same object instance, nothing is changed on the stack. - We call
startvirtual method on theProcessBuilderinstance. The resultingProcessis pushed back to the stack. - We call
popto remove the returnedProcessinstance and basically "ignore" it while cleaning up the stack. - We
returnfrom the method.
It doesn't seem there's a lot of space for optimizations, besides one thing - the pop instruction at the end is meant to make sure the stack gets cleaned up, but I've discovered that at least in my case, the JVM doesn't care (maybe because my method is the main method). Thus, I can save one more byte, reducing my solution to 338 bytes!
00000000│cafe babe 0000 003d│001c 0100 046d 6169│.......=.....mai
00000010│6e07 0004 0100 0443│6f64 6501 000c 6a61│n......Code...ja
00000020│7661 2f69 6f2f 4669│6c65 0100 063c 696e│va/io/File...<in
00000030│6974 3e07 000c 0700│0801 0018 6a61 7661│it>.........java
00000040│2f6c 616e 672f 5072│6f63 6573 7342 7569│/lang/ProcessBui
00000050│6c64 6572 0700 0a01│0010 6a61 7661 2f6c│lder......java/l
00000060│616e 672f 5374 7269│6e67 0800 0c01 0004│ang/String......
00000070│6375 726c 0800 0e01│0002 2d4c 0800 1001│curl......-L....
00000080│0005 3766 2e75 6b0a│0007 0012 0c00 0500│..7f.uk.........
00000090│1301 0016 285b 4c6a│6176 612f 6c61 6e67│....([Ljava/lang
000000a0│2f53 7472 696e 673b│2956 0a00 0700 150c│/String;)V......
000000b0│0016 0017 0100 0969│6e68 6572 6974 494f│.......inheritIO
000000c0│0100 1c28 294c 6a61│7661 2f6c 616e 672f│...()Ljava/lang/
000000d0│5072 6f63 6573 7342│7569 6c64 6572 3b0a│ProcessBuilder;.
000000e0│0007 0019 0c00 1a00│1b01 0005 7374 6172│............star
000000f0│7401 0015 2829 4c6a│6176 612f 6c61 6e67│t...()Ljava/lang
00000100│2f50 726f 6365 7373│3b04 2100 0600 0200│/Process;.!.....
00000110│0000 0000 0100 0900│0100 1300 0100 0300│................
00000120│0000 2d00 0600 0100│0000 21bb 0007 5906│..-.......!...Y.
00000130│bd00 0959 0312 0b53│5904 120d 5359 0512│...Y...SY...SY..
00000140│0f53 b700 11b6 0014│b600 18b1 0000 0000│.S..............
00000150│0000 │ │..
I've decided to examine the Runtime.getRuntime().exec() option - until this point it was not appealing due to the need to divert the standard output which still requires ProcessBuilder.
However, the option became more appealing mostly due to Runtime not being final - that means my class could inherit from Runtime - as long as I can find a trick to divert the standard output.
So, the challenges I am facing are:
- As I mentioned, even if your class is
abstract- the compiler would still try to create a constructor (<init>) - and even if you create one yourself, the compiler would make sure to callsuper()on it - and there's no constructor forRuntimethat gets 0 arguments. I solved that by inheriting fromObjectand patching the class later. - Diverting the standard output. I ended up doing a weird trick:
sh -c "curl 7f.uk>/dev/ttysolved the problem - which means my solution wouldn't work on Windows, but I think it's still a cool idea. - I still have to remove the
Exceptionhandling later asRuntime.exec()throws.
I've decided to call my class Code, which would still re-use the mandatory Code string. My base code was this:
public abstract class Code {
public static void main(String[] args) throws Exception {
Runtime.getRuntime().exec(new String[] {"sh", "-c", "curl -L 7f.uk>/dev/tty"});
}
}I've applied all we learned:
- Removing
Exceptionhandling in themainmethod's second attribute (calledExceptions). - Getting rid of
<init>. - Inheriting from
Runtimerather thanObject. - Removing all unnecessary entries from the constant pool.
- Removing the last
popinstruction from our bytecode.
And - success! My class is now only 314 bytes long, and works well!
00000000│cafe babe 0000 003d│0017 0a00 0200 0307│.......=........
00000010│0004 0c00 0500 0601│0011 6a61 7661 2f6c│..........java/l
00000020│616e 672f 5275 6e74│696d 6501 000a 6765│ang/Runtime...ge
00000030│7452 756e 7469 6d65│0100 1528 294c 6a61│tRuntime...()Lja
00000040│7661 2f6c 616e 672f│5275 6e74 696d 653b│va/lang/Runtime;
00000050│0700 0801 0010 6a61│7661 2f6c 616e 672f│......java/lang/
00000060│5374 7269 6e67 0800│0a01 0002 7368 0800│String......sh..
00000070│0c01 0002 2d63 0800│0e01 0016 6375 726c│....-c......curl
00000080│202d 4c20 3766 2e75│6b3e 2f64 6576 2f74│ -L 7f.uk>/dev/t
00000090│7479 0a00 0200 100c│0011 0012 0100 0465│ty.............e
000000a0│7865 6301 0028 285b│4c6a 6176 612f 6c61│xec..(([Ljava/la
000000b0│6e67 2f53 7472 696e│673b 294c 6a61 7661│ng/String;)Ljava
000000c0│2f6c 616e 672f 5072│6f63 6573 733b 0700│/lang/Process;..
000000d0│1401 0004 436f 6465│0100 046d 6169 6e01│....Code...main.
000000e0│0016 285b 4c6a 6176│612f 6c61 6e67 2f53│..([Ljava/lang/S
000000f0│7472 696e 673b 2956│0421 0013 0002 0000│tring;)V.!......
00000100│0000 0001 0009 0015│0016 0001 0014 0000│................
00000110│0026 0005 0001 0000│001a b800 0106 bd00│.&..............
00000120│0759 0312 0953 5904│120b 5359 0512 0d53│.Y...SY...SY...S
00000130│b600 0fb1 0000 0000│0000 │..........
Some of you might wonder why I haven't used th exec method that gets a single string. Well, the reason is that I didn't use it is how Java's implementation works.
You see, exec(String) is a convenience method that would internally just split the string to whitespaces and feed it in an array. Note that I still need to use >/dev/tty, so I have to use sh.
My plan was to do the following:
Runtime.getRuntime().exec("sh -c curl -L 7f.uk>/dev/tty");Alas, it'd get split by Java, so the equivalent is this:
Runtime.getRuntime.exec(new String[5] { "sh", "-c", "curl", "-L", "7f.uk>/dev/tty" });This isn't good - I need to work around that. Well, the trick that I found is pretty neat - I use the shell's parameter expansion (which means I have to use bash and not sh) to get an existing environment variable with a space in it. Luckily, $IFS is literally the variable that I need - it contains all the token-splitters for the commandline.
Therefore, my goal is running this:
Runtime.getRuntime().exec("bash -c curl${IFS:1:1}-L${IFS:1:1}7f.uk>/d*/tty");Note that I also used /d*/tty to shorten /dev by one character - neat!
After a hint from Gynvael Coldwind I understood we could even improve ${IFS:1:1} to just be ${IFS}.
As usual, I applied all the tricks from before - luckily my code is much smaller (bytecode is only 9 bytes now!) and the constant pool has only 16 entries. In total - we are at 275 bytes!
The bytecode:
b8 00 01 invokestatic java/lang/Runtime::getRuntime()Ljava/lang/Runtime; (C1)
12 02 ldc "bash -c curl${IFS}-L${IFS}7f.uk>/d*/tty" (C2)
b6 00 03 invokevirtual java/lang/Runtime::exec(Ljava/lang/String;)Ljava/lang/Process; (C3)
b1 returnThe content:
00000000│cafe babe 0000 0037│0011 0a00 0800 0908│.......7........
00000010│000a 0a00 0800 0b07│0005 0100 0443 6f64│.............Cod
00000020│6501 0004 6d61 696e│0100 1628 5b4c 6a61│e...main...([Lja
00000030│7661 2f6c 616e 672f│5374 7269 6e67 3b29│va/lang/String;)
00000040│5607 000c 0c00 0d00│0e01 0027 6261 7368│V..........'bash
00000050│202d 6320 6375 726c│247b 4946 537d 2d4c│ -c curl${IFS}-L
00000060│247b 4946 537d 3766│2e75 6b3e 2f64 2a2f│${IFS}7f.uk>/d*/
00000070│7474 790c 000f 0010│0100 116a 6176 612f│tty........java/
00000080│6c61 6e67 2f52 756e│7469 6d65 0100 0a67│lang/Runtime...g
00000090│6574 5275 6e74 696d│6501 0015 2829 4c6a│etRuntime...()Lj
000000a0│6176 612f 6c61 6e67│2f52 756e 7469 6d65│ava/lang/Runtime
000000b0│3b01 0004 6578 6563│0100 2728 4c6a 6176│;...exec..'(Ljav
000000c0│612f 6c61 6e67 2f53│7472 696e 673b 294c│a/lang/String;)L
000000d0│6a61 7661 2f6c 616e│672f 5072 6f63 6573│java/lang/Proces
000000e0│733b 0421 0004 0008│0000 0000 0001 0009│s;.!............
000000f0│0006 0007 0001 0005│0000 0015 0002 0001│................
00000100│0000 0009 b800 0112│02b6 0003 b100 0000│................
00000110│0000 00 │ │...
On macOS you can even go for /d*/*y as a replacement for /dev/tty but I decided I want to support both macOS and Linux (in which /d*/*y is not unique on some distros).
I've also decided to upload a new utility called [miniclass-exec.py] that gets a commandline and writes a new Code.class file to disk.
The cool thing is that it tries to use the ${IFS:1:1} trick to any whitespace, and also tries to minimize the file size by attempting to use variables.
For example, echo hello world how are you will turn into bash -c A=${IFS};echo${A}hello${A}world${A}how${A}are${A}you.
All in all, we found good ways of minimizing the class:
- Implement our class
abstract, thus getting rid of<init>completely. - Inheriting from a class different than
Object- shortest name I could find wasjava/io/Filebut in the case ofRuntimeI could reuseRuntimeas a superclass, which is only possible due removing<init>entirely. - Getting rid of
Exceptionhandling. - Not cleaning up the stack.
- Re-using strings (e.g. calling your class
Codeorcurlin our case). - Using things like
/dev/ttyto write to the output and substrings of$IFSto work-around spaces.
I gotta say, Binary Golf is super fun and makes you learn new things every day!
I really enjoy the challenge and considering playing some more, maybe with an Android App this time.
Stay tuned!
Jonathan Bar Or
