├── .gitignore ├── 001-Breaking-The-Verifier-1 ├── Test.jasm └── index.md ├── 002-Breaking-The-Verifier-2 ├── Cargo.toml ├── Illegal.jasm ├── Test.java ├── index.md ├── run.sh └── src │ └── lib.rs ├── 003-Exploiting-ASM-1 ├── Exploit.jcod ├── index.md └── test.sh ├── 004-Bitcoin-vs-Nano └── index.md ├── 005-Installing-Arch-Chromebook ├── batteryconnector.webp └── index.md ├── 006-Mixed-Boolean-Arithmetic └── index.md ├── 007-InvokeDynamic-Explained ├── Test.java └── index.md ├── LICENSE.md ├── README.md ├── asmtools.jar ├── filters ├── getpostdetails.lua ├── lineallcode.lua ├── math2svg.lua └── wordcount.lua ├── make.py ├── resources ├── CC-BY-SA_icon.svg ├── index.css ├── post.css └── styles.css └── templates ├── 404.html ├── emptytemplate.html ├── index.html ├── maintemplate.html ├── post.html ├── posts.html └── tag.html /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | target/ 4 | out/ 5 | Cargo.lock 6 | hs_err* 7 | *.class 8 | *.html 9 | !/resources/* 10 | !/templates/* 11 | *.bak 12 | pandoc 13 | -------------------------------------------------------------------------------- /001-Breaking-The-Verifier-1/Test.jasm: -------------------------------------------------------------------------------- 1 | super public class Test 2 | extends "sun/reflect/MagicAccessorImpl" 3 | version 52:0 4 | { 5 | public static Method main:"([Ljava/lang/String;)V" 6 | stack 2 locals 1 7 | { 8 | getstatic java/lang/System.out:"Ljava/io/PrintStream;"; 9 | ldc "Hello, world!"; 10 | invokevirtual java/io/PrintStream.println:"(Ljava/lang/String;)V"; 11 | 12 | // Throw and catch a string 13 | try T1; 14 | 15 | ldc "You should not be able to throw a string!"; 16 | athrow; 17 | 18 | endtry T1; 19 | catch T1 java/lang/String; 20 | 21 | // print the string we caught 22 | getstatic java/lang/System.out:"Ljava/io/PrintStream;"; 23 | swap; 24 | invokevirtual java/io/PrintStream.println:"(Ljava/lang/String;)V"; 25 | 26 | return; 27 | } 28 | } -------------------------------------------------------------------------------- /001-Breaking-The-Verifier-1/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Breaking The Verifier #1" 3 | author: x4e 4 | keywords: [jvm,verifier] 5 | description: "Bypassing the OpenJDK8 and OpenJ9 verifier through an unsecured backdoor" 6 | date: 16th October 2020 7 | --- 8 | 9 | ## Some background 10 | 11 | The JVM was originally designed with web usage in mind. 12 | The idea was that you could run Java Applications from your web browser, and while this was popular, chrome began to start the process of removing it in 2015. 13 | 14 | Obviously, for people to be able to run arbitrary Java applications in your browser there needs to be a form of security protection. 15 | The JVM has three main enforcers of security: 16 | 17 | - Access Checker 18 | - Checks access flags for things like fields, classes and methods 19 | - Enforced at native level 20 | - Verifier 21 | - Verifies that classes contain "legal" bytecode 22 | - Enforced at native level 23 | - Security Manager 24 | - Is able to prohibit actions such as accessing the file system 25 | - Enforced at Java level 26 | - Inactive in a regular java instance 27 | 28 | In this post I am going to be breaking all three of these mechanisms. 29 | 30 | ## Why does the JVM have a verifier? 31 | 32 | Java is compiled into an intermediate bytecode, which must follow [a strict set of rules defined by Oracle](https://docs.oracle.com/javase/specs/jvms/se15/html/). 33 | By having a well-defined specification of easy to understand bytecode, users are easily able to disassemble bytecode before running it on their machines, so that they can make sure they only run code that they trust. 34 | It also means checks can be made to ensure, for example, that a jump instruction doesn't jump to arbitrary memory addresses, for example on the heap (allowing generating code at runtime), or inside another method). 35 | 36 | ## Breaking it 37 | 38 | [xDark](https://github.com/xxDark) and I were spending a bit of time recently looking into potential JVM security flaws, when he stumbled across an interesting piece of code (So of course full credit for this goes to him, I am just creating a write-up). 39 | 40 | Take a look at the following code from [reflection.cpp#L455](https://github.com/openjdk/jdk/blob/jdk8-b120/hotspot/src/share/vm/runtime/reflection.cpp#L455): 41 | ```{.cpp startFrom="455"} 42 | bool Reflection::verify_class_access(Klass* current_class, Klass* new_class, bool classloader_only) { 43 | // Verify that current_class can access new_class. If the classloader_only 44 | // flag is set, we automatically allow any accesses in which current_class 45 | // doesn't have a classloader. 46 | if ((current_class == NULL) || 47 | (current_class == new_class) || 48 | (new_class->is_public()) || 49 | is_same_class_package(current_class, new_class)) { 50 | return true; 51 | } 52 | // New (1.4) reflection implementation. Allow all accesses from 53 | // sun/reflect/MagicAccessorImpl subclasses to succeed trivially. 54 | if ( JDK_Version::is_gte_jdk14x_version() 55 | && UseNewReflection 56 | && current_class->is_subclass_of(SystemDictionary::reflect_MagicAccessorImpl_klass())) { 57 | return true; 58 | } 59 | 60 | return can_relax_access_check_for(current_class, new_class, classloader_only); 61 | } 62 | ``` 63 | This method is called each time a class links against another class. 64 | It returns true if the class has permission to access the other class. 65 | The basic implementation checks that the class is public or in the same package. 66 | 67 | However, in Java 4 the reflection API was added. Because reflection inherently allows Java code to bypass access controls, a backdoor was added in the form of the class `sun.reflect.MagicAccessorImpl`. 68 | Java classes that are necessary for facilitating the reflection API can simply extend this class to *magically* bypass any access controls. Of course, if any class could bypass access flags (without using the reflection API which is guarded by the Security Manager) this would be a security risk. 69 | The solution was to make MagicAccessorImpl package private, meaning only other sun.reflect classes can access it. 70 | 71 | There's one problem with this though: If you try to load a class that extends the magic accessor it will call `verify_class_access` to check if you can indeed access the magic accessor (which of course we shouldn't be able to). This method will see that you extend magic accessor, and therefore allow you to extend magic accessor. 72 | 73 | By now we have broken the first stage of JVM security, the access checker. By simply extending a specific class we can bypass all access restrictions. 74 | 75 | Now we will use this to break the second two. 76 | 77 | 78 | [verifier.cpp#L188](https://github.com/openjdk/jdk/blob/jdk8-b120/hotspot/src/share/vm/classfile/verifier.cpp#L188) 79 | ```{.cpp startFrom="188"} 80 | bool Verifier::is_eligible_for_verification(instanceKlassHandle klass, bool should_verify_class) { 81 | Symbol* name = klass->name(); 82 | Klass* refl_magic_klass = SystemDictionary::reflect_MagicAccessorImpl_klass(); 83 | 84 | bool is_reflect = refl_magic_klass != NULL && klass->is_subtype_of(refl_magic_klass); 85 | 86 | return (should_verify_for(klass->class_loader(), should_verify_class) && 87 | // return if the class is a bootstrapping class 88 | // or defineClass specified not to verify by default (flags override passed arg) 89 | // We need to skip the following four for bootstraping 90 | name != vmSymbols::java_lang_Object() && 91 | name != vmSymbols::java_lang_Class() && 92 | name != vmSymbols::java_lang_String() && 93 | name != vmSymbols::java_lang_Throwable() && 94 | 95 | // Can not verify the bytecodes for shared classes because they have 96 | // already been rewritten to contain constant pool cache indices, 97 | // which the verifier can't understand. 98 | // Shared classes shouldn't have stackmaps either. 99 | !klass()->is_shared() && 100 | 101 | // As of the fix for 4486457 we disable verification for all of the 102 | // dynamically-generated bytecodes associated with the 1.4 103 | // reflection implementation, not just those associated with 104 | // sun/reflect/SerializationConstructorAccessor. 105 | // NOTE: this is called too early in the bootstrapping process to be 106 | // guarded by Universe::is_gte_jdk14x_version()/UseNewReflection. 107 | // Also for lambda generated code, gte jdk8 108 | (!is_reflect || VerifyReflectionBytecodes)); 109 | } 110 | ``` 111 | 112 | This backdoor into the access checker didn't completely allow the reflection API to function correctly, there was a bug. 113 | 114 | The code does reference a bug code (`4486457`) but it seems to be private. There is however [an interesting email chain related to it](http://mail.openjdk.java.net/pipermail/jigsaw-dev/2016-December/010645.html): 115 | 116 | > No it isn't public. Basically when the code-generating reflection 117 | > mechanism was introduced verification had to be bypassed because the 118 | > generated code didn't obey the expected subclassing rules for protected 119 | > access - hence MagicAccessor. 120 | 121 | What this essentially means is that verification will be **completely** disabled for all subclasses of MagicAccessor. 122 | I've written a test class Test.jasm to test this. The class can be decompiled to something like below: 123 | ```java 124 | public class Test extends sun.reflect.MagicAccessorImpl { 125 | public static void main(String[] args) { 126 | System.out.println("Hello, world!"); 127 | 128 | try { 129 | throw "You should not be able to throw a string!"; 130 | } catch (String exception) { 131 | System.out.println(exception); 132 | } 133 | } 134 | } 135 | ``` 136 | Obviously this code is invalid! You should not be able to throw a string. 137 | 138 | Now compile [Test.jasm](https://github.com/x4e/Blog/blob/master/001-Breaking-The-Verifier-1/Test.jasm) and run `java Test` making sure that you are using java version 8. As you can see we throw, catch and print the string. 139 | 140 | The second layer of the JVM's security is broken: we can load and execute completely illegal classes. 141 | With this power we can now also trivially break the third layer: we can simply overwrite the `java.lang.System.securityManager` field with `null` to disable the security manager. Since we are doing a direct field set instead of `System.setSecurityManager` the security manager itself has no option to prevent this. And since the access checker is giving us magic powers, we are not prevented by the JVM. 142 | 143 | ## Future JVMs 144 | Sadly this was broken after Java 8 with the introduction of the module system. Magic Accessor was relocated into the `jvm.internal` package which is in a module protected from user classes. -------------------------------------------------------------------------------- /002-Breaking-The-Verifier-2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "NoVerify" 3 | version = "0.1.0" 4 | authors = ["mastercooker "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | libc = "0.2.79" 12 | rs-jvm-bindings = { git = "https://github.com/cookiedragon234/rs-jvm-bindings/" } 13 | detour = { version = "0.7.1", default-features = false } 14 | -------------------------------------------------------------------------------- /002-Breaking-The-Verifier-2/Illegal.jasm: -------------------------------------------------------------------------------- 1 | super public class Illegal 2 | version 50:0 3 | { 4 | public Method "":"()V" 5 | stack 2 locals 1 6 | { 7 | aload_0; 8 | invokespecial Method java/lang/Object."":"()V"; 9 | return; 10 | } 11 | 12 | public static Method "":"()V" 13 | stack 2 locals 1 14 | { 15 | getstatic java/lang/System.out:"Ljava/io/PrintStream;"; 16 | ldc "Hello, world!"; 17 | invokevirtual java/io/PrintStream.println:"(Ljava/lang/String;)V"; 18 | 19 | // Throw and catch a string 20 | try T1; 21 | 22 | ldc "You should not be able to throw a string!"; 23 | athrow; 24 | 25 | endtry T1; 26 | catch T1 java/lang/String; 27 | 28 | // print the string we caught 29 | getstatic java/lang/System.out:"Ljava/io/PrintStream;"; 30 | swap; 31 | invokevirtual java/io/PrintStream.println:"(Ljava/lang/String;)V"; 32 | 33 | return; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /002-Breaking-The-Verifier-2/Test.java: -------------------------------------------------------------------------------- 1 | public class Test { 2 | public static void main(String[] args) throws Throwable { 3 | System.loadLibrary("verify"); 4 | System.loadLibrary("NoVerify"); 5 | run(); 6 | System.out.println("Done"); 7 | System.out.println(Class.forName("Illegal")); 8 | } 9 | 10 | public static native void run(); 11 | public static native void test(); 12 | } 13 | -------------------------------------------------------------------------------- /002-Breaking-The-Verifier-2/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Breaking The Verifier #2" 3 | author: x4e 4 | keywords: [jvm,verifier] 5 | description: "Breaking the verifier for all OpenJDK 8+ JVMs by hooking shared library exports" 6 | date: 17th October 2020 7 | --- 8 | 9 | Breaking the verifier for all OpenJDK 8+ JVMs by hooking shared library exports. 10 | 11 | Make sure you've read #1 before this, so you have a basic understanding of what the verifier is and why it's important. 12 | 13 | 14 | ## Some background 15 | 16 | Before Java 6, classes were verified in two stages: 17 | 18 | * First, the type-inferencing verifier did type emulations of methods in order to predict the types of any values used 19 | * Secondly, the type-checking verifier verified that types were used accurately 20 | 21 | A team working on the "Connected Limited Device Configuration" complained that this process, specifically the first stage, was slow and expensive to perform, particularly on embedded systems. They proposed the split verifier. 22 | 23 | The split verifier has similar stages to the original verifier, except that the first stage is performed at compile time, and embedded into the class file's "StackMapTable" attributes by the compiler. 24 | This shifts the cost to compile time, potentially speeding up runtime class loading. 25 | 26 | The split verifier was initially intended to be shipped with Java 5 (Tiger release), but landed in Java 6, where the Java compiler had an optional experimental flag to generate the StackMapTable attributes, and the JVM would only use them if they were present. 27 | 28 | In Java 7 the StackMapTables were made mandatory for any version 7 class files, and the compiler produced them by default. 29 | However, to support older class files, the old verification method (now bundled externally in `verify.dll` or `libverify.so`) is used for any class files version 6 or less. 30 | 31 | 32 | ## Breaking this 33 | 34 | Let's take a look at the JVM code for loading the `verify` dynamic library that contains the split verifier 35 | 36 | [verifier.cpp#L66](https://github.com/openjdk/jdk/blob/976acddeb5a8df1e868269787c023306aad3fe4a/src/hotspot/share/classfile/verifier.cpp#L66): 37 | ```{.cpp startFrom="66"} 38 | // Access to external entry for VerifyClassForMajorVersion - old byte code verifier 39 | 40 | extern "C" { 41 | typedef jboolean (*verify_byte_codes_fn_t)(JNIEnv *, jclass, char *, jint, jint); 42 | } 43 | 44 | static verify_byte_codes_fn_t volatile _verify_byte_codes_fn = NULL; 45 | 46 | static verify_byte_codes_fn_t verify_byte_codes_fn() { 47 | 48 | if (_verify_byte_codes_fn != NULL) 49 | return _verify_byte_codes_fn; 50 | 51 | MutexLocker locker(Verify_lock); 52 | 53 | if (_verify_byte_codes_fn != NULL) 54 | return _verify_byte_codes_fn; 55 | 56 | // Load verify dll 57 | char buffer[JVM_MAXPATHLEN]; 58 | char ebuf[1024]; 59 | if (!os::dll_locate_lib(buffer, sizeof(buffer), Arguments::get_dll_dir(), "verify")) 60 | return NULL; // Caller will throw VerifyError 61 | 62 | void *lib_handle = os::dll_load(buffer, ebuf, sizeof(ebuf)); 63 | if (lib_handle == NULL) 64 | return NULL; // Caller will throw VerifyError 65 | 66 | void *fn = os::dll_lookup(lib_handle, "VerifyClassForMajorVersion"); 67 | if (fn == NULL) 68 | return NULL; // Caller will throw VerifyError 69 | 70 | return _verify_byte_codes_fn = CAST_TO_FN_PTR(verify_byte_codes_fn_t, fn); 71 | } 72 | ``` 73 | 74 | Quite simply, the JVM loads the `verify` library, then searches for the `VerifyClassForMajorVersion` function within it. 75 | 76 | How can we exploit this? 77 | 78 | We can: 79 | 80 | 1. Get a handle to the same library 81 | 2. Find the same function 82 | 3. Hook said function 83 | 84 | To implement this I will be using rust. Sources are included in [src/lib.rs](https://github.com/x4e/Blog/blob/master/002-Breaking-The-Verifier-2/src/lib.rs). 85 | I've only implemented this for Linux but feel free to extend it to other operating systems. Should only require `dlopen` and `dlsym` being replaced with alternatives. 86 | 87 | First I need to find the folder where java will store its libraries. This is stored in the property `sun.boot.library.path`. 88 | ```rust 89 | let system: jclass = (**env).FindClass.unwrap()(env, to_c_str("java/lang/System")); 90 | let method: jmethodID = (**env).GetStaticMethodID.unwrap()(env, system, to_c_str("getProperty"), to_c_str("(Ljava/lang/String;)Ljava/lang/String;")); 91 | let name: jstring = (**env).NewStringUTF.unwrap()(env, to_c_str("sun.boot.library.path")); 92 | let args: Vec = vec![jvalue { l: name }; 1]; 93 | let out: jstring = (**env).CallStaticObjectMethodA.unwrap()(env, system, method, args.as_ptr()); 94 | assert!(!out.is_null()); 95 | PATH = Some(from_c_str((**env).GetStringUTFChars.unwrap()(env, out, null_mut()))); 96 | ``` 97 | 98 | Now I can retrieve a handle to the `verify` DLL: 99 | ```rust 100 | let dl: *mut c_void = dlopen(to_c_str(format!("{}/libverify.so", PATH.clone().unwrap())), RTLD_LAZY); 101 | ``` 102 | 103 | And then retrieve a pointer to the verify function: 104 | ```rust 105 | let symbol_ptr: *mut c_void = dlsym(dl, to_c_str("VerifyClassForMajorVersion")); 106 | ``` 107 | 108 | Now I will use the `detour` crate to hook the method, this just modifies the function assembly to call my own function instead: 109 | ```rust 110 | pub unsafe extern "C" fn dont_verify_lol(_env: *mut c_void, _class: *mut c_void, _buffer: *mut c_char, _len: c_int, _major_version: c_int) -> c_uchar { 111 | // 1 == class file is legal 112 | return 1; 113 | } 114 | 115 | let hook = RawDetour::new(symbol_ptr as *const (), dont_verify_lol as *const ()) 116 | .expect("target or source is not usable for detouring"); 117 | hook.enable().expect("Couldn't enable hook"); 118 | HOOK = Some(hook); 119 | ``` 120 | 121 | And that's it... Now any class verified with the split verifier will instantly pass verification. 122 | 123 | You can test this by running [run.sh](https://github.com/x4e/Blog/blob/master/002-Breaking-The-Verifier-2/run.sh). 124 | 125 | 126 | 127 | ## Edit 1 128 | 129 | As it turns out, this can be used to bypass verification on up to **Java 7** class files. That's pretty big for obfuscators, as Java 7 still supports features like InvokeDynamic, and it is possible to convert Java 8 classes into Java 7. 130 | 131 | It turns out that the JVM is able to fail over to the old split verifier if a class file (with a version <=J7) fails the new verification 132 | 133 | [verifier.cpp#L194](https://github.com/openjdk/jdk/blob/976acddeb5a8df1e868269787c023306aad3fe4a/src/hotspot/share/classfile/verifier.cpp#L194) 134 | ```{.cpp startFrom="194"} 135 | ClassVerifier split_verifier(klass, THREAD); 136 | split_verifier.verify_class(THREAD); 137 | exception_name = split_verifier.result(); 138 | 139 | bool can_failover = !DumpSharedSpaces && 140 | klass->major_version() < NOFAILOVER_MAJOR_VERSION; 141 | 142 | if (can_failover && !HAS_PENDING_EXCEPTION && // Split verifier doesn't set PENDING_EXCEPTION for failure 143 | (exception_name == vmSymbols::java_lang_VerifyError() || 144 | exception_name == vmSymbols::java_lang_ClassFormatError())) { 145 | log_info(verification)("Fail over class verification to old verifier for: %s", klass->external_name()); 146 | log_info(class, init)("Fail over class verification to old verifier for: %s", klass->external_name()); 147 | message_buffer = NEW_RESOURCE_ARRAY(char, message_buffer_len); 148 | exception_message = message_buffer; 149 | exception_name = inference_verify( 150 | klass, message_buffer, message_buffer_len, THREAD); 151 | } 152 | if (exception_name != NULL) { 153 | exception_message = split_verifier.exception_message(); 154 | } 155 | ``` 156 | -------------------------------------------------------------------------------- /002-Breaking-The-Verifier-2/run.sh: -------------------------------------------------------------------------------- 1 | cargo build 2 | java -jar ../asmtools.jar jasm Illegal.jasm 3 | javac Test.java 4 | java -Xlog:verification -Djava.library.path=./target/debug/ Test 5 | -------------------------------------------------------------------------------- /002-Breaking-The-Verifier-2/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::os::raw::{c_void, c_int, c_char, c_uchar}; 2 | use std::ptr::null_mut; 3 | use rs_jvm_bindings::jni::{JavaVM, JNI_VERSION_1_6, JNIEnv, jclass, jmethodID, jstring, jvalue}; 4 | use std::ffi::CString; 5 | use detour::RawDetour; 6 | use std::borrow::Borrow; 7 | use libc::{dlsym, dlopen}; 8 | 9 | fn to_c_str>>(str: T) -> *const c_char { 10 | let cstr = CString::new(str).unwrap(); 11 | cstr.into_raw() 12 | } 13 | 14 | unsafe fn from_c_str(str: *const c_char) -> String { 15 | assert!(!str.is_null()); 16 | let cstr = CString::from_raw(str as *mut c_char); 17 | cstr.into_string().unwrap() 18 | } 19 | 20 | const RTLD_LAZY: c_int = 0x00001; 21 | 22 | type VerifyMethodType = extern fn(env: *mut c_void, class: *mut c_void, buffer: *mut c_char, len: c_int, major_version: c_int) -> c_uchar; 23 | 24 | pub unsafe extern "C" fn dont_verify_lol(_env: *mut c_void, _class: *mut c_void, _buffer: *mut c_char, _len: c_int, _major_version: c_int) -> c_uchar { 25 | return 1; 26 | } 27 | 28 | #[no_mangle] 29 | pub unsafe extern "system" fn JNI_OnLoad(_vm: *mut JavaVM, _reserved: &mut c_void) -> c_int { 30 | JNI_VERSION_1_6 as i32 31 | } 32 | 33 | static mut PATH: Option = None; 34 | static mut HOOK: Option = None; 35 | 36 | #[no_mangle] 37 | pub unsafe extern "system" fn Java_Test_run(env: *mut JNIEnv, _class: jclass) { 38 | let system: jclass = (**env).FindClass.unwrap()(env, to_c_str("java/lang/System")); 39 | let method: jmethodID = (**env).GetStaticMethodID.unwrap()(env, system, to_c_str("getProperty"), to_c_str("(Ljava/lang/String;)Ljava/lang/String;")); 40 | let name: jstring = (**env).NewStringUTF.unwrap()(env, to_c_str("sun.boot.library.path")); 41 | let args: Vec = vec![jvalue { l: name }; 1]; 42 | let out: jstring = (**env).CallStaticObjectMethodA.unwrap()(env, system, method, args.as_ptr()); 43 | assert!(!out.is_null()); 44 | PATH = Some(from_c_str((**env).GetStringUTFChars.unwrap()(env, out, null_mut()))); 45 | 46 | let dl: *mut c_void = dlopen(to_c_str(format!("{}/libverify.so", PATH.clone().unwrap())), RTLD_LAZY); 47 | 48 | let symbol_ptr: *mut c_void = dlsym(dl, to_c_str("VerifyClassForMajorVersion")); 49 | 50 | let hook = RawDetour::new(symbol_ptr as *const (), dont_verify_lol as *const ()) 51 | .expect("target or source is not usable for detouring"); 52 | hook.enable().expect("Couldn't enable hook"); 53 | HOOK = Some(hook); 54 | } 55 | 56 | 57 | #[no_mangle] 58 | pub unsafe extern "system" fn Java_Test_test(_env: *mut JNIEnv, _class: jclass) { 59 | // test hook is active 60 | let hook = HOOK.borrow().as_ref().unwrap(); 61 | assert!(hook.is_enabled()); 62 | 63 | let dl: *mut c_void = dlopen(to_c_str(format!("{}/libverify.so", PATH.clone().unwrap())), RTLD_LAZY); 64 | 65 | let symbol_ptr: *mut c_void = dlsym(dl, to_c_str("VerifyClassForMajorVersion")); 66 | let symbol: VerifyMethodType = std::mem::transmute(symbol_ptr); 67 | symbol(null_mut(), null_mut(), null_mut(), 0, 0); 68 | } 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /003-Exploiting-ASM-1/Exploit.jcod: -------------------------------------------------------------------------------- 1 | class Exploit { 2 | 0xCAFEBABE; 3 | 0; // minor version 4 | 52; // major version (8) 5 | [] { // Constant Pool 6 | ; // first element is null 7 | Utf8 "Exploit"; // #1 8 | Utf8 "java/lang/Object"; // #2 9 | class #1; // #3 10 | class #2; // #4 11 | Utf8 ""; // #5 12 | Utf8 "()V"; // #6 13 | Utf8 "Code"; // #7 14 | Utf8 "main"; // #8 15 | Utf8 "([Ljava/lang/String;)V"; // #9 16 | Utf8 "NestMembers"; // #10 17 | 18 | Utf8 "java/lang/System"; // #11 19 | class #11; // #12 20 | Utf8 "out"; // #13 21 | Utf8 "Ljava/io/PrintStream;"; // #14 22 | 23 | NameAndType #13 #14; // #15 24 | Field #12 #15; // #16 25 | 26 | Utf8 "java/io/PrintStream"; // #17 27 | class #17; // #18 28 | Utf8 "println"; // #19 29 | Utf8 "(Ljava/lang/String;)V"; // #20 30 | NameAndType #19 #20; // #21 31 | Method #18 #21; // #22 32 | 33 | Utf8 "Hello World!"; // #23 34 | String #23 // #24 35 | } 36 | 37 | 0x0021; // access 38 | #3;// this_cpx 39 | #4;// super_cpx 40 | 41 | [] {} // Interfaces 42 | [] {} // fields 43 | 44 | [] { // methods 45 | { 46 | 0x0009; // access 47 | #8; // name_cpx 48 | #9; // sig_cpx 49 | [] { // Attributes 50 | Attr(#7) { // Code 51 | 2; // max_stack 52 | 1; // max_locals 53 | Bytes[]{ 54 | 0xB2; // getstatic 55 | 0x0010; // #16 56 | 0x12; // ldc 57 | 0x18; // #24 58 | 0xB6; // invokevirtual 59 | 0x0016; // #22 60 | 0xB1; // return 61 | }; 62 | [] {} // Traps 63 | [] {} // Attributes 64 | } 65 | } 66 | } 67 | } 68 | 69 | [] { // Attributes 70 | Attr(#10) {} // NestMembers Attribute, completely empty 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /003-Exploiting-ASM-1/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Exploiting the OW2 ASM Library's Parser" 3 | author: x4e 4 | keywords: [jvm,obfuscation,ow2asm] 5 | description: "Finding and exploiting a buffer overflow in the OW2 ASM Library" 6 | date: 28th January 2021 7 | --- 8 | 9 | One common tool used for Reverse Engineering is [The OW2 ASM Library](https://gitlab.ow2.org/asm/asm/), a library that can read, manipulate and write classfiles generated by the javac compiler. This makes ASM a target for obfuscation -- hindering the functionality of ASM on obfuscated classes can be very valuable. 10 | 11 | In this post I'll go over how I found and exploited a design flaw in the ASM library in order to create classfiles that render ASM useless. 12 | 13 | ## Well Known Attributes 14 | 15 | The [JVM classfile specification](https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-4.html) defines the format of binary classfiles that can be loaded and executed by the Java Virtual Machine. The classfile format includes lists of the fields, methods and bytecode in a classfile for example. 16 | 17 | Many of the structures in the classfile format, for example fields and methods, contain a list of attributes; attributes are simply a section of arbitrary bytes with a user defined name, similar to adding a custom section in an ELF file. For example, a custom attribute could be used to provide extra metadata to debuggers. While some attributes can be user defined, other attributes are "Well Known", meaning that the name and structure of the attribute is known to and hardcoded within the JVM. 18 | 19 | An example of this is the `Code` attribute, which contains information about the bytecode of a method; When the JVM encounters an attribute within a method with the name `Code` it will be parsed according to [the definition of the code attribute within the JVM specification](https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-4.html#jvms-4.7.3). 20 | 21 | What is interesting however is when a new Java release, and therefore a new version of the classfile specification, results in the addition of a new well known attribute. This happened in Java 11, when the introduction of Nest-Based Access Control (JEP 181: Allow separate classes to access each other's private members) resulted in the `NestMembers` attribute being [defined in the JVM specification](https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-4.html#jvms-4.7.29). 22 | 23 | There is an issue here -- since compilers are allowed to insert user defined attributes into classfiles, what would happen if a Java 10 classfile with a custom attribute happening to be called `NestMembers` is loaded onto a Java 11 virtual machine? Well luckily the JVM developers thought of this and the JVM will not parse a well known attribute if the classfile version is less than the version in which the attribute was defined. 24 | 25 | ## The exploit 26 | 27 | While the JVM developers took this issue into consideration, the ASM library developers did not. The library will always try to parse well known attributes, no matter the classfile version. What this means is that we could for example add an attribute with the name `NestMembers` and a length of 0 to a version 8 classfile. ASM would attempt to parse the NestMembers attribute and would basically buffer overflow as it attempts to read data from a 0 length attribute. Sadly (or maybe luckily!) since ASM is written in Java, a memory safe language, all a buffer overflow means here is that an exception is thrown. 28 | 29 | I've written an example classfile to demonstrate this behaviour which can be viewed [here](https://github.com/x4e/Blog/blob/master/003-Exploiting-ASM-1/Exploit.jcod), which you can test by running the provided [test.sh script](https://github.com/x4e/Blog/blob/master/003-Exploiting-ASM-1/test.sh). The classfile simply prints "Hello World!" but the key part is the attribute defined at the bottom of it. Essentially this classfile will run absolutely fine on any JVM, however ASM will crash upon attempting to parse the file with the following stack trace: 30 | 31 | ```java 32 | java.lang.ArrayIndexOutOfBoundsException: Index 317 out of bounds for length 317 33 | at org.objectweb.asm.ClassReader.readUnsignedShort(ClassReader.java:3561) 34 | at org.objectweb.asm.ClassReader.accept(ClassReader.java:660) 35 | at org.objectweb.asm.ClassReader.accept(ClassReader.java:394) 36 | ``` 37 | 38 | Looking at [the relevant code](https://gitlab.ow2.org/asm/asm/-/blob/ASM_9_0/asm/src/main/java/org/objectweb/asm/ClassReader.java#L461) we can see the fault: 39 | 40 | ```{.java startFrom="461"} 41 | // - The offset of the NestMembers attribute, or 0. 42 | int nestMembersOffset = 0; 43 | ... 44 | int currentAttributeOffset = getFirstAttributeOffset(); 45 | for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) { 46 | // Read the attribute_info's attribute_name and attribute_length fields. 47 | String attributeName = readUTF8(currentAttributeOffset, charBuffer); 48 | int attributeLength = readInt(currentAttributeOffset + 2); 49 | currentAttributeOffset += 6; 50 | 51 | if (Constants.SOURCE_FILE.equals(attributeName)) { 52 | sourceFile = readUTF8(currentAttributeOffset, charBuffer); 53 | ... 54 | } else if (Constants.NEST_MEMBERS.equals(attributeName)) { 55 | nestMembersOffset = currentAttributeOffset; 56 | ... 57 | } 58 | currentAttributeOffset += attributeLength; 59 | } 60 | ... 61 | // Visit the NestedMembers attribute. 62 | if (nestMembersOffset != 0) { 63 | int numberOfNestMembers = readUnsignedShort(nestMembersOffset); 64 | int currentNestMemberOffset = nestMembersOffset + 2; 65 | while (numberOfNestMembers-- > 0) { 66 | classVisitor.visitNestMember(readClass(currentNestMemberOffset, charBuffer)); 67 | currentNestMemberOffset += 2; 68 | } 69 | } 70 | ... 71 | ``` 72 | 73 | The problem is easily visible here -- the `NestMembers` attribute is always parsed if present, no matter the classfile version. This can be compared to the behaviour of [the Open JDK's classfile parser](https://github.com/openjdk/jdk/blob/0da9cad5f55713bc81f3a0689b8836ff548ad0cf/src/hotspot/share/classfile/classFileParser.cpp#L3714), which clearly avoids this mistake with an `if (_major_version >= JAVA_11_VERSION)`: 74 | 75 | ```{.cpp startFrom="3714"} 76 | // Iterate over attributes 77 | while (attributes_count--) { 78 | cfs->guarantee_more(6, CHECK); // attribute_name_index, attribute_length 79 | const u2 attribute_name_index = cfs->get_u2_fast(); 80 | const u4 attribute_length = cfs->get_u4_fast(); 81 | check_property( 82 | valid_symbol_at(attribute_name_index), 83 | "Attribute name has bad constant pool index %u in class file %s", 84 | attribute_name_index, CHECK); 85 | const Symbol* const tag = cp->symbol_at(attribute_name_index); 86 | if (tag == vmSymbols::tag_source_file()) { 87 | ... 88 | } else if (_major_version >= JAVA_11_VERSION) { 89 | if (tag == vmSymbols::tag_nest_members()) { 90 | // Check for NestMembers tag 91 | if (parsed_nest_members_attribute) { 92 | classfile_parse_error("Multiple NestMembers attributes in class file %s", THREAD); 93 | return; 94 | } else { 95 | parsed_nest_members_attribute = true; 96 | } 97 | if (parsed_nest_host_attribute) { 98 | classfile_parse_error("Conflicting NestHost and NestMembers attributes in class file %s", THREAD); 99 | return; 100 | } 101 | nest_members_attribute_start = cfs->current(); 102 | nest_members_attribute_length = attribute_length; 103 | cfs->skip_u1(nest_members_attribute_length, CHECK); 104 | } else if (tag == vmSymbols::tag_nest_host()) { 105 | ``` 106 | 107 | I discovered this exploit while analysing ASM and included it within [my obfuscator](https://binclub.dev/binscure) last year, however since it has now been copied by some of my competitors I thought it would be best to release it as a write-up. Hopefully this helps other people writing classfile parsers to know which pitfalls to avoid. 108 | -------------------------------------------------------------------------------- /003-Exploiting-ASM-1/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | java -jar ../asmtools.jar jcoder Exploit.jcod 4 | java Exploit 5 | -------------------------------------------------------------------------------- /004-Bitcoin-vs-Nano/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Why Bitcoin Has Failed - and the Future of Electronic Cash" 3 | author: x4e 4 | keywords: [cryptocurrency] 5 | description: "A criticism of Bitcoin and an evaluation of it's competitors" 6 | date: 4th February 2021 7 | --- 8 | 9 | Bitcoin's white paper presents it as a "peer-to-peer electronic cash system" ^[], however I do not believe that Bitcoin has fulfilled the requirements of cash. 10 | 11 | ## The problems with Bitcoin 12 | 13 | Firstly, here is the absolute minimum I expect out of a form of cash: 14 | 15 | * Widely recognised as being of value 16 | * Irreversible transactions 17 | * Easy to hold reasonable amounts 18 | * Hard to forge (transactions and balances) 19 | * Easy and fast to transfer reasonable amounts 20 | 21 | Each of these are satisfied by my local fiat, GBP, however they are not all satisfied by Bitcoin: 22 | 23 | * Bitcoin is recognised as being of value nowadays, this is satisfied 24 | * Bitcoin transactions cannot be reversed (at least certainly after they have been accepted into a block) 25 | * Vast amounts of Bitcoin are easily held 26 | * It is impractical to forge transactions and balances are impossible to forged 27 | * While it is easy to transfer Bitcoin, it is certainly not fast. The current average time between blocks is just over 9 minutes, and your transaction will only be in the next block if you pay the most competitive fee. 28 | 29 | Bitcoin was designed before cryptocurrencies had ever received any major attention: Satoshi did not imagine, prepare or test for the amount of pressure that is currently being exerted on the system. It was only long after Satoshi last went offline that Bitcoin began to be picked up by major media outlets and normal people began to here about it. 30 | 31 | In 2017/2018 Bitcoin experienced parabolic growth and attention, causing the network to come under extreme strain from the amount and size of transactions taking place. A combination of huge fees and transaction waiting times highlighted the impracticality of Bitcoin as a form of cash and probably contributed to it's price decline, since back then Bitcoin's price speculation was based on it being a form of payment rather than a store of value. 32 | 33 | The technical reason behind Bitcoin's lack of scalability is the block size. Every transaction is included in a block which is then committed to the blockchain, larger blocks allow more transactions to be processed at a time. When Bitcoin was created there was actually no intention to have a block size limit, but one did effectively exist due to some related code. Satoshi manually added a 1 MB block size limit in 2010 which became effective in 2013 after the unintended block size limit was removed. 34 | 35 | There are some reasons why a larger block size could not be desirable: 36 | 37 | * It can require all nodes to be able to process blocks up to the largest size, this increases the hardware requirement of running a node, therefore making nodes less common and more likely to be owned by the big mining companies 38 | * Fewer nodes would reduce miner competition and more expensive to run nodes would increase costs, both potentially leading to higher fees 39 | * The network could be subject to denial of service attacks if large blocks are continuously forced through the network 40 | 41 | 42 | ## Bitcoin Cash 43 | 44 | Bitcoin Cash (BCH) was created as a hard fork of Bitcoin in 2017, with its first change being an upgrade to an 8 MB maximum block size. This was intended to be a solution to Bitcoin's scalability. 45 | 46 | Since the fork, Bitcoin Cash itself has hard forked multiple times, splitting the community into Bitcoin Cash Node (BCH/BCHN), Bitcoin Cash ABC (BCHA), and Bitcoin Satoshi's Vision (BSV), with Bitcoin Cash Node having the majority consensus and as such retaining the BCH tickers. 47 | 48 | Each of these forks have split the community and developers, and many communities still call the minority forks Bitcoin Cash, creating a lot of confusion. For example, [bitcoincash.org](https://www.bitcoincash.org/) seems to promote BCH however it is operated by BCHA and leads users to BCHA services. 49 | 50 | Decentralised currencies only work when consensus can be reached. Because of this split, unclear, and disagreeing community I do not have much hope for the future of BCH and I do not hold any. 51 | 52 | 53 | ## Nano 54 | 55 | [Nano](https://nano.org) was created under the name RaiBlocks (XRB) in 2014/15. Nano uses a block lattice to give each account its own blockchain, with each transaction being its own block. Since there are multiple blockchains, transactions on each blockchain can be performed asynchronously. 56 | 57 | Nano also does not have every node validating blocks, instead users vote with their account balance for a representative who will perform validation for them. Representatives do not gain any monetary reward, resulting in a more decentralised network as nodes are typically run by individuals rather than large mining companies. This also allows transactions to have 0 fees. 58 | 59 | Nano has an average transaction confirmation time of 0.2 seconds and has succeeded tests of handling up to 1800 transactions per second (compared to bitcoin's 3-4). 60 | 61 | 62 | ## Conclusion 63 | 64 | Many currencies have tried to improve upon Bitcoin, like Bitcoin Cash and Nano. These don't seem to have gained as much attention as the Ethereum competitors have recently, but they will still have a large effect on the future of Cryptocurrencies. Bitcoin Cash attempts to build upon Bitcoin's design by making tweaks to components such as the block size, whereas Nano is an entirely new protocol designed to be asynchronous, fast and fee-less. 65 | -------------------------------------------------------------------------------- /005-Installing-Arch-Chromebook/batteryconnector.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x4e/Blog/0b630c3e4bb52d5ded9595635aca346768e9504c/005-Installing-Arch-Chromebook/batteryconnector.webp -------------------------------------------------------------------------------- /005-Installing-Arch-Chromebook/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Installing Arch Linux on my Chromebook" 3 | author: x4e 4 | keywords: [linux,hardware] 5 | description: "A walkthrough of how I installed Linux on my ApolloLake Chromebook" 6 | date: 20th February 2021 7 | resources: [batteryconnector.webp] 8 | --- 9 | 10 | I bought a Chromebook a couple of years ago to do school work on — it only cost about £100 and was very small and light, perfect for carrying around school. At the time I had never experienced Linux and was fine with storing all my stuff on Google Docs/drive etc. 11 | 12 | Last year I switched to Linux on my main laptop (one that's too large to use for school), and since then I've always wanted to be able to install it on my Chromebook. Unfortunately that's not easy — Chromebooks use a lot of obscure hardware for which drivers are only implemented in Chrome OS and not upstream Linux. Google also licenses Chromebook manufacturing and some parts of design to other manufactures, meaning that each Chromebook can be very different, and as such there is no single guide to install Linux on a Chromebook. 13 | 14 | My particular Chromebook is an Asus c223n with an ApolloLake processor. Here are the steps I took to install Arch Linux: 15 | 16 | 1. [Enable developer mode](https://www.howtogeek.com/210817/how-to-enable-developer-mode-on-your-chromebook/). This will result in a warning screen showing every time you boot up your computer - just press Control+D at this screen to boot Chrome OS as normal, or press Control+L for the SeaBIOS menu (for booting to a USB or other install). 17 | 18 | 2. In ApolloLake the firmware read/write protection is active when the battery is plugged in (as opposed to a screw being present in other Chromebooks). To disable this open up the back of the Laptop and disconnect the battery from the motherboard (connected via 8 multicoloured wires). This is done by pulling the metallic holder off the top of the white cable head, then pulling the cable head up and out of the socket. I just folded the wires back so that the cable head was in between the bottom of the battery and the laptop case. This will of course mean you will need a stable power connection during the rest of the installation. 19 | 20 | ![Image of battery power connector](batteryconnector.webp) 21 | 22 | 3. Boot using the power cable as the power source and open Chrome. Press Control+Alt+T to open crosh and use the `shell` command to open a shell. Use [Mr Chromebox's script](https://mrchromebox.tech/#fwscript) to install RW_LEGACY firmware. 23 | 24 | 4. Turn the Chromebook off and plug in a bootable USB containing the Arch Installation ISO. 25 | 26 | 5. Turn the Chromebook on and press Control+L at the developer mode boot screen to use the alternate boot menu. Follow the steps to boot from the USB. 27 | 28 | 6. Connect to Wi-Fi (`ip link set wlan0 up`, `iwctl --passphrase PASSPHRASE station wlan0 connect SSID`, `dhcpcd wlan0`). 29 | 30 | 7. Partition the disk to look something like below (partitions have been sorted by layout which they weren't previously, and partitions 1-5 were originally on the chromebook and I have kept them): 31 | ``` 32 | Device Start End Sectors Size Type 33 | /dev/mmcblk1p1 64 16447 16384 8M unknown 34 | /dev/mmcblk1p2 16448 16448 1 512B ChromeOS kernel 35 | /dev/mmcblk1p3 16449 16449 1 512B ChromeOS root fs 36 | /dev/mmcblk1p4 16450 16450 1 512B ChromeOS reserved 37 | /dev/mmcblk1p5 16451 16451 1 512B ChromeOS reserved 38 | /dev/mmcblk1p6 18432 1363967 1345536 657M EFI System 39 | /dev/mmcblk1p11 1363968 9752575 8388608 4G Linux swap 40 | /dev/mmcblk1p12 9752576 61071326 51318751 24.5G Linux root (x86-64) 41 | ``` 42 | Also make sure to format the root and boot partition as well as enabling the swap partition. 43 | 44 | 8. Follow official installation instructions until Boot Loader section, where I followed [the Syslinux installation instructions](https://wiki.archlinux.org/index.php/Syslinux). 45 | 46 | 9. Reboot and everything should work fine! (Remember to press Control+L at the developer boot warning screen). 47 | 48 | The Syslinux + SeaBIOS combination creates a slightly weird boot process. First the BIOS shows a developer mode warning, then SeaBIOS loads Syslinux, then Syslinux loads Linux, then Linux loads my login manager. A lot of steps that can go wrong - but it works for now. 49 | [Boot process](https://www.youtube.com/watch?v=wbdMpEMTalw). 50 | 51 | ## Suspend 52 | 53 | Apparently on Apollo Lake processors using suspend can cause the BIOS to enter a recovery mode (which may prevent booting into Linux?). Apparently this might have been fixed in recent kernels, but I am not sure. Personally I haven't had a need to use suspend yet. 54 | 55 | ## Audio 56 | 57 | Audio sadly doesn't work on the chromebook. I did find [this blog post](https://bkhome.org/news/201912/sound-fix-for-54-kernel-on-apollo-lake.html) which looks to be the same problem but the solution didn't work for me - maybe it will for you. Also, apparently [this modified 4.4 kernel](https://gitlab.com/moussaelianarsen/chromebook-linux-audio/-/tree/master/) works, but I do not want to go to that much effort. 58 | -------------------------------------------------------------------------------- /006-Mixed-Boolean-Arithmetic/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Mixed Boolean Arithmetic Obfuscation" 3 | author: x4e 4 | keywords: [jvm,bytecode,obfuscation] 5 | description: "Obfuscating mathematical operations with MBA" 6 | date: 29th March 2021 7 | --- 8 | 9 | Mathematical operations are central to modern computers, with every program making extensive use of them. 10 | Algorithms for purposes such as licensing, encryption and hashing make extensive use of arithmetic operations and by nature have very high security requirement. 11 | The ability to obfuscate the operations behind such algorithms can be very useful. 12 | 13 | ## Boolean Arithmetic 14 | 15 | Boolean arithmetic is the basis of all computers. 16 | It operates on binary numbers using the basic logical instructions such as AND, OR, and NOT. 17 | These are typically implemented as simple combinations of NAND gates -- every logical operation can be implemented in only NAND gates. 18 | 19 | Boolean operations are typically compounds of others. 20 | The most basic example is a NAND gate which is simply a combination of an AND and a NOT. 21 | On a more complex level, every single logic gate is usually implemented as a combination of NAND gates. 22 | With a couple of NAND gates you can simulate every other logical operation. 23 | 24 | The ability to combine boolean operations to simulate others allows options for obfuscation. 25 | A simple recognisable operation can be swapped for a combination of other operations whose purpose may not be easily understood. 26 | 27 | There are many substitution rules that can be used to swap out complex boolean expressions for simplified ones. 28 | De Morgan's laws are an example of this. 29 | They are as follows: 30 | $$ \lnot (\lnot x \land \lnot y) \equiv x \lor y $$ 31 | $$ \lnot (\lnot x \lor \lnot y) \equiv x \land y $$ 32 | 33 | While these rules can be used to simplify expressions, they can also be used for the opposite purpose: to obfuscate an expression. 34 | By simply swapping them around we get another perspective: 35 | $$ x \lor y \equiv \lnot (\lnot x \land \lnot y) $$ 36 | 37 | We can very easily use this substitution to obscure code, however it is not particularly good -- it is very repetitive and patterns emerge very clearly. 38 | 39 | To increase the diversity of the substitution, we can create our own and mix them together. 40 | Let's consider the purpose of an XOR operation (operating on a single bit). An XOR operation will return true if the two inputs are not equal to each other. 41 | $$ x \oplus y \equiv x \not\equiv y $$ 42 | 43 | Since in this model there are only two possible states of both x and y (0 and 1) we can expand this into either x being 1 and y being 0, or x being 0 and y being 1: 44 | $$ x \oplus y \equiv (x \land \lnot y) \lor (\lnot x \land y) $$ 45 | 46 | This has now given us a very simple XOR substitution where the XOR is only on one bit (for example between booleans). 47 | 48 | ## Integer Arithmetic 49 | 50 | Integers aren't something a circuit can traditionally deal with, but by combining logical operations to deal with signs and carrying it is possible to support them. 51 | 52 | One traditional integer circuit is a full adder, this is a circuit that can take two binary numbers and add them together. 53 | To demonstrate the ability to implement integer operations at a boolean level, I wrote this software emulator of a 32bit full adder using only boolean operations: 54 | ```Java 55 | public class Adder { 56 | // Number of bits in an int, excluding sign 57 | private static final int BITS = 31; 58 | // A mask for each bit except the most significant (the sign) 59 | private static final int MSB_MASK = 60 | Integer.parseInt("01111111111111111111111111111111", 2); 61 | // A mask for the least significant bit 62 | private static final int LSB_MASK = 63 | Integer.parseInt("00000000000000000000000000000001", 2); 64 | 65 | private int carry; 66 | public final int result; 67 | 68 | public Adder(int x, int y) { 69 | int current = 0; 70 | for (int i = 0; i < BITS; i++) { 71 | // Add the least significant bits together 72 | int tmp = addBits(x & LSB_MASK, y & LSB_MASK); 73 | // Remove least significant bits 74 | x = x >> 1; 75 | y = y >> 1; 76 | 77 | // Add to most significant bit of result 78 | tmp = tmp << BITS; 79 | current = current | tmp; 80 | // Move it right 81 | current = current >> 1; 82 | // Don't allow the shift to change the sign 83 | current &= MSB_MASK; 84 | } 85 | this.result = current; 86 | } 87 | 88 | // Add together the least significant bits of two ints 89 | // Respects and updates the carry flag 90 | private int addBits(int x, int y) { 91 | int xor = x ^ y; 92 | int result = xor ^ carry; 93 | carry = (x & y) | (xor & carry); 94 | return result; 95 | } 96 | 97 | public static int add(int x, int y) { 98 | return new Adder(x, y).result; 99 | } 100 | 101 | public static void main(String[] args) { 102 | int i1 = Integer.parseInt(args[0]); 103 | int i2 = Integer.parseInt(args[1]); 104 | int sum = add(i1, i2); 105 | String f = "%d + %d = %d"; 106 | System.out.println(f.formatted(i1, i2, sum)); 107 | } 108 | } 109 | ``` 110 | This is obviously very inefficient due to being implemented in software, and also it is missing some optimisations used by modern computers, for example, calculating the carries all at once and then adding the bits in parallel. 111 | 112 | While substituting an addition with a software level addition emulation would be relatively costly, there are much simpler integer substitutions that we can perform with the help of our boolean operators. 113 | 114 | Of course the most obvious substitution might be: 115 | $$ x + y \equiv y + x $$ 116 | 117 | This is not very obscure however. 118 | What we can do is emulate two's complement maths to obscure this addition. 119 | On a hardware level there is normally no implementation of a subtraction circuit, this would be very complicated, so instead we rely on another quite obvious substitution -- we rely on the double negative: 120 | $$ x - y \equiv x + -y $$ 121 | 122 | In practice what this means is that, in a two's complement system, a computer can store the negative of a number $x$ as: 123 | $$ -x \equiv \lnot x + 1 $$ 124 | $$ -0010 \equiv 1101 + 1 \equiv 1110 $$ 125 | 126 | The bit inversion also conveniently inverts the sign (the left most bit). 127 | 128 | Now if we perform a simple addition between the two complement number's $0010$ ($2$) and $1110$ ($-2$) we get $0000$ ($0$). 129 | Note that this requires us to ignore the overflow. 130 | ``` 131 | |- overflow 132 | V 133 | carry: 11100 134 | 0010 135 | + 1110 136 | = 0000 137 | ``` 138 | 139 | This can be written in the form of a substitution as: 140 | $$ x - y \equiv x + (\lnot y + 1) $$ 141 | 142 | Which can also be rewritten as: 143 | $$ x + y \equiv x - (\lnot y - 1) $$ 144 | 145 | Unlike the simple $x + y \equiv y + x$ this substitution is much more complicated to understand and requires knowledge of both boolean integer representation (two's complement), boolean operations ($\lnot$), and integer operations (subtraction). 146 | 147 | ## Application 148 | 149 | In the above code we came up with an effective substitution for a very common integer operation (subtraction) and also applied it to another (addition). 150 | Unlike naive substitutions that may substitute within the same class of expression, our substitution mixes both integer and boolean operations, forcing the reader to evaluate many different styles of mathematics to understand the code's purpose. 151 | One way the effectiveness of these substitutions can also be improved is by recursively substituting within previous substitutions. 152 | 153 | As noted at the start of this post, these substitution methods would be very useful within code obfuscation, especially when cryptography is involved. 154 | In my obfuscator, [Binscure](https://binclub.dev/binscure), we recursively apply lots of mixed substitutions to obscure the original operations -- here is an example of the obfuscated equivalent of the full adder I wrote above: 155 | ```Java 156 | // Decompiled with: CFR 0.151 157 | // Class Version: 15 158 | public class Adder { 159 | public static int BITS = 31; 160 | public static int LSB_MASK; 161 | public static int MSB_MASK; 162 | public int carry; 163 | public int result; 164 | 165 | public static int add(int n, int n2) { 166 | return new Adder((int)n, (int)n2).result; 167 | } 168 | 169 | public int addBits(int n, int n2) { 170 | int n3; 171 | int n4 = n; 172 | int n5 = n2; 173 | int n6 = (n4 | ~n5) - ~n5 - n4 - 1; 174 | int n7 = (n5 & ~n6) * 2 - (n5 ^ n6); 175 | int n8 = (n7 ^ 1) - (1 & ~n7) * 2; 176 | int n9 = ~n4 + 1; 177 | int n10 = (n9 | 0xFFFFFFFF) * 2 - ~n9; 178 | int n11 = n5 - (n10 & n5); 179 | int n12 = (n10 ^ n11) + (n10 & n11) * 2; 180 | int n13 = ~(n4 - 1); 181 | int n14 = (n13 | 0xFFFFFFFF) * 2 - ~n13; 182 | int n15 = n12 & ~n14 | n14 & ~n12; 183 | int n16 = ((n14 | n12) - n12) * 2; 184 | int n17 = (n15 ^ n16) - (n16 & ~n15) * 2; 185 | int n18 = n17 + (n8 & ~n17); 186 | int n19 = ((n18 & ~n17) - (n17 & ~n18)) * 2; 187 | int n20 = n17 + (n8 & ~n17); 188 | int n21 = (n17 | ~n8) - ~n8; 189 | int n22 = (n20 & ~n21) - (n21 & ~n20); 190 | int n23 = ((n19 | n22) - n22) * 2; 191 | int n24 = (n19 | n22) & ~(n19 & n22); 192 | int n25 = n3 = (n23 & ~n24) - (n24 & ~n23); 193 | int n26 = this.carry; 194 | int n27 = n25 + ~((n25 | ~n26) - ~n26) + 1; 195 | int n28 = (n27 + (n26 & ~n27)) * 2 + ~(n26 & ~n27 | n27 & ~n26) + 1; 196 | int n29 = ~(n25 - 1); 197 | int n30 = (n29 | 0xFFFFFFFF) * 2 - ~n29; 198 | int n31 = (n26 | n30) - n30; 199 | int n32 = (n30 | n31) * 2 - (n30 ^ n31); 200 | int n33 = ~(n25 - 1) - ~-1 - 1; 201 | int n34 = (n32 | n33) - n33 + ~(n33 - (n32 & n33)) + 1; 202 | int n35 = (n28 | ~n34) - ~n34; 203 | int n36 = ((n28 ^ n35) - (n35 & ~n28) * 2) * 2; 204 | int n37 = (n34 | n28) - n28; 205 | int n38 = n37 + ((n28 | n34) - n34 & ~n37); 206 | int n39 = (n36 | n38) - (n36 & n38) + ~(((n38 | n36) - n36) * 2) + 1; 207 | int n40 = n; 208 | int n41 = 1 - n40 - 1; 209 | int n42 = (-1 + (n41 & ~-1)) * 2; 210 | int n43 = n41 & ~-1 | 0xFFFFFFFF & ~n41; 211 | int n44 = (n42 & ~n43) * 2 - (n42 ^ n43); 212 | int n45 = n44 + (n2 & ~n44); 213 | int n46 = (n45 & ~n44) - (n44 & ~n45); 214 | int n47 = (n46 + (n44 & ~n46)) * 2 + ~((n44 | n46) & (~n46 | ~n44)) + 1; 215 | int n48 = 1 + ~n40; 216 | int n49 = (-1 + (n48 & ~-1)) * 2; 217 | int n50 = (n48 | 0xFFFFFFFF) & (~-1 | ~n48); 218 | int n51 = ~((n49 & ~n50) - (n50 & ~n49)) + 1; 219 | int n52 = -((n51 | 0xFFFFFFFF) + (n51 & 0xFFFFFFFF)) + -1; 220 | int n53 = (n47 ^ n52) - (n52 & ~n47) * 2; 221 | int n54 = (n53 & ~1) - (1 & ~n53); 222 | int n55 = (1 + (n54 & ~1)) * 2; 223 | int n56 = (n54 | 1) & ~(n54 & 1); 224 | int n57 = n3; 225 | int n58 = -n57 + -1; 226 | int n59 = (n58 | 1) * 2 - (n58 ^ 1); 227 | int n60 = (-1 + (n59 & ~-1)) * 2 + ~((n59 | 0xFFFFFFFF) - (n59 & 0xFFFFFFFF)) + 1; 228 | int n61 = n60 + (this.carry & ~n60); 229 | int n62 = (n61 & ~n60) - (n60 & ~n61); 230 | int n63 = n62 + (n60 & ~n62); 231 | int n64 = (n62 | ~n60) - ~n60; 232 | int n65 = (n63 | n64) * 2 - (n63 ^ n64); 233 | int n66 = 1 + ~n57; 234 | int n67 = -1 + (n66 & ~-1); 235 | int n68 = (0xFFFFFFFF | ~n66) - ~n66; 236 | int n69 = (n67 | n68) * 2 - (n67 ^ n68); 237 | int n70 = n69 + (n65 & ~n69); 238 | int n71 = -n65 + -1; 239 | int n72 = (n71 + (-n69 + -1 & ~n71) | ~n70) - ~n70; 240 | int n73 = (n69 | ~n65) - ~n65; 241 | int n74 = ((n69 & ~n73) * 2 - (n69 ^ n73)) * 2; 242 | int n75 = (n72 | n74) - n74; 243 | int n76 = (n74 | n72) - n72; 244 | int n77 = (n75 & ~n76) - (n76 & ~n75); 245 | int n78 = n77 - ~(((n55 & ~n56) - (n56 & ~n55) | n77) - n77) - 1; 246 | int n79 = (n78 | n77) - n77; 247 | int n80 = n77 - (n78 & n77); 248 | int n81 = (n79 & ~n80) * 2 - (n79 ^ n80); 249 | int n82 = n77 - (n81 & n77); 250 | int n83 = (n81 | n82) * 2 - (n81 ^ n82); 251 | int n84 = -n77 + -1; 252 | int n85 = n84 + (n81 & ~n84); 253 | int n86 = -n77 + -1; 254 | int n87 = (n85 & ~n86) - (n86 & ~n85); 255 | this.carry = (n87 + (n83 & ~n87)) * 2 + ~(n83 & ~n87 | n87 & ~n83) + 1; 256 | return n39; 257 | } 258 | 259 | public Adder(int n, int n2) { 260 | int n3 = 0; 261 | for (int i = 0; i < 31; ++i) { 262 | int n4 = n; 263 | int n5 = 1 - n4 - 1; 264 | int n6 = -1 + (n5 & ~-1); 265 | int n7 = (0xFFFFFFFF | ~n5) - ~n5; 266 | int n8 = (n6 | n7) * 2 - (n6 ^ n7); 267 | int n9 = LSB_MASK; 268 | int n10 = (n9 | ~n8) - ~n8; 269 | int n11 = (n9 & ~n10) * 2 - (n9 ^ n10); 270 | int n12 = n11 + (n8 & ~n11); 271 | int n13 = (n11 | ~n8) - ~n8; 272 | int n14 = (n12 | n13) * 2 - (n12 ^ n13); 273 | int n15 = 1 - n4 - 1; 274 | int n16 = (n15 | 0xFFFFFFFF) - (n15 & 0xFFFFFFFF) - ~(((0xFFFFFFFF | ~n15) - ~n15) * 2) - 1; 275 | int n17 = (n14 | ~n16) - ~n16; 276 | int n18 = (n14 & ~n17) - (n17 & ~n14); 277 | int n19 = n14 + (n16 & ~n14); 278 | int n20 = (n19 & ~n14) * 2 - (n19 ^ n14); 279 | int n21 = n2; 280 | int n22 = 1 - n21 - 1; 281 | int n23 = (-1 + (n22 & ~-1)) * 2; 282 | int n24 = n22 & ~-1 | 0xFFFFFFFF & ~n22; 283 | int n25 = (n23 & ~n24) * 2 - (n23 ^ n24); 284 | int n26 = n25 + (LSB_MASK & ~n25) + ~n25 + 1; 285 | int n27 = (n25 | n26) & ~(n25 & n26); 286 | int n28 = ((n26 | ~n25) - ~n25) * 2; 287 | int n29 = (n27 | n28) + (n27 & n28); 288 | int n30 = -n21 + -1; 289 | int n31 = (n30 | 1) + (n30 & 1); 290 | int n32 = --1 + -1; 291 | int n33 = ~((n31 & ~n32) * 2 - (n31 ^ n32) + ~1 + 1) + 1 - ~-1 - 1; 292 | int n34 = n33 + (n29 & ~n33); 293 | int n35 = (n33 | ~n29) - ~n29; 294 | int n36 = (n34 | n35) + (n34 & n35); 295 | int n37 = this.addBits(((n18 | n20) - n20) * 2 + ~((n18 | n20) & (~n20 | ~n18)) + 1, (n36 | 1) - (n36 & 1) - ~(((1 | ~n36) - ~n36) * 2) - 1); 296 | n >>= 1; 297 | n2 >>= 1; 298 | int n38 = n37 <<= 31; 299 | int n39 = n3; 300 | int n40 = -n38 + -1; 301 | int n41 = n40 + (n39 & ~n40); 302 | int n42 = -n38 + -1; 303 | int n43 = (n41 ^ n42) - (n42 & ~n41) * 2; 304 | int n44 = -n39 + -1; 305 | int n45 = (n43 | n44) + (n43 & n44); 306 | int n46 = ((n38 | n45) - n45) * 2; 307 | int n47 = (n38 | n45) & ~(n38 & n45); 308 | int n48 = (n46 ^ n47) - (n47 & ~n46) * 2; 309 | int n49 = (n48 - (1 & n48)) * 2; 310 | int n50 = (n48 | 1) - (n48 & 1); 311 | n3 = (n49 & ~n50) * 2 - (n49 ^ n50); 312 | int n51 = n3 >>= 1; 313 | int n52 = 1 + ~n51; 314 | int n53 = --1 + -1; 315 | int n54 = (n52 ^ n53) - (n53 & ~n52) * 2; 316 | int n55 = (n54 ^ 1) - (1 & ~n54) * 2; 317 | int n56 = MSB_MASK; 318 | int n57 = (n56 | ~n55) - ~n55; 319 | int n58 = (n56 & ~n57) - (n57 & ~n56); 320 | int n59 = (n58 + (n55 & ~n58)) * 2; 321 | int n60 = n55 & ~n58 | n58 & ~n55; 322 | int n61 = (n59 ^ n60) - (n60 & ~n59) * 2; 323 | int n62 = -n51 + -1; 324 | int n63 = (n62 ^ 1) + (n62 & 1) * 2; 325 | int n64 = -1 + (n63 & ~-1); 326 | int n65 = (0xFFFFFFFF | ~n63) - ~n63; 327 | int n66 = (n64 | n65) + (n64 & n65); 328 | int n67 = n66 + (n61 & ~n66); 329 | int n68 = (n67 & ~n66) * 2 - (n67 ^ n66); 330 | int n69 = n61 + (n66 & ~n61) + ~n61 + 1; 331 | int n70 = (n68 | n69) - (n68 & n69); 332 | int n71 = ((n69 | n68) - n68) * 2; 333 | n3 = (n70 ^ n71) - (n71 & ~n70) * 2; 334 | } 335 | this.result = n3; 336 | } 337 | 338 | static { 339 | MSB_MASK = Integer.parseInt("01111111111111111111111111111111", 2); 340 | LSB_MASK = Integer.parseInt("00000000000000000000000000000001", 2); 341 | } 342 | 343 | public static void main(String[] stringArray) { 344 | int n = Integer.parseInt(stringArray[0]); 345 | int n2 = Integer.parseInt(stringArray[1]); 346 | int n3 = Adder.add(n, n2); 347 | String string = "%d + %d = %d"; 348 | System.out.println(string.formatted(n, n2, n3)); 349 | } 350 | } 351 | 352 | ``` 353 | In fact, this code will completely crash some decompilers like JetBrain's Fernflower: 354 | ```Java 355 | java.lang.OutOfMemoryError: Java heap space 356 | at org.jetbrains.java.decompiler.util.FastSparseSetFactory$FastSparseSet.(FastSparseSetFactory.java:84) 357 | at org.jetbrains.java.decompiler.util.FastSparseSetFactory$FastSparseSet.(FastSparseSetFactory.java:69) 358 | at org.jetbrains.java.decompiler.util.FastSparseSetFactory.spawnEmptySet(FastSparseSetFactory.java:57) 359 | at org.jetbrains.java.decompiler.modules.decompiler.sforms.SSAUConstructorSparseEx.setCurrentVar(SSAUConstructorSparseEx.java:699) 360 | at org.jetbrains.java.decompiler.modules.decompiler.sforms.SSAUConstructorSparseEx.processExprent(SSAUConstructorSparseEx.java:363) 361 | at org.jetbrains.java.decompiler.modules.decompiler.sforms.SSAUConstructorSparseEx.processExprent(SSAUConstructorSparseEx.java:231) 362 | at org.jetbrains.java.decompiler.modules.decompiler.sforms.SSAUConstructorSparseEx.processExprent(SSAUConstructorSparseEx.java:231) 363 | at org.jetbrains.java.decompiler.modules.decompiler.sforms.SSAUConstructorSparseEx.processExprent(SSAUConstructorSparseEx.java:231) 364 | at org.jetbrains.java.decompiler.modules.decompiler.sforms.SSAUConstructorSparseEx.processExprent(SSAUConstructorSparseEx.java:231) 365 | ... 366 | ``` 367 | 368 | If you are interested in learning about more mixed boolean substitutions I recommend reading Hacker's Delight by Henry S. Warren, Jr. 369 | -------------------------------------------------------------------------------- /007-InvokeDynamic-Explained/Test.java: -------------------------------------------------------------------------------- 1 | import java.lang.reflect.*; 2 | import java.lang.invoke.*; 3 | import java.util.*; 4 | 5 | public class Test { 6 | public static void main(String[] args) throws Throwable { 7 | var method = System.class.getDeclaredMethod("exit", int.class); 8 | method.invoke(null, 2); 9 | 10 | MethodHandles.Lookup lookup = MethodHandles.lookup(); 11 | var mh = lookup.findStatic(System.class, "exit", MethodType.methodType(void.class, int.class)); 12 | mh.invokeExact(2); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /007-InvokeDynamic-Explained/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "What are InvokeDynamics and how do they work?" 3 | author: x4e 4 | keywords: [jvm,bytecode] 5 | description: "An explanation of what the invokedynamic instruction is and how it works" 6 | date: 27th April 2021 7 | unlisted: true 8 | --- 9 | 10 | In the Java Virtual Machine version 7 the `CONSTANT_InvokeDynamic` constant pool type was introduced, followed by its cousin the `CONSTANT_Dynamic` type in version 11. 11 | These two types are very complicated to understand and there seems to be a lack of sufficient resources to teach their purpose, functionality and use, so that is what I will attempt to do. 12 | 13 | # Method Handles 14 | 15 | A method handle is an instance of the Java runtime abstract class `java.lang.invoke.MethodHandle`. 16 | This abstract class allows the invocation of a MethodHandle similar to that of a class like `java.util.function.Consumer` -- you have methods such as `invoke` which is able to take arguments and give back a return value. 17 | 18 | In practice, method handles usually refer to something like a method, field, or constructor and when invoked will peform operations such as calling the method, setting/getting the field, or allocating an object and calling it's constructor. 19 | Method handles can also be a transformation of another method handle; one possible transformation might be to spread a given array argument into individual arguments in the underlying method handle -- this transformation is actually provided by `MethodHandle.asSpreader`[^1]. 20 | 21 | [^1]: 22 | 23 | Method handles also store a "type" which describes the parameters it should be expecting and the return type you should be expecting. 24 | 25 | ## Signature Polymorphic Methods 26 | 27 | Before we look at polymorphic signatures let's just remind ourselves of the Java types: 28 | 29 | |Descriptor|Description | 30 | |----------|--------------------------------| 31 | |`L[name];`|Reference to an Object | 32 | |`I` |32bit integer | 33 | |`B` |Byte - erased to I at runtime | 34 | |`C` |Char - erased to I at runtime | 35 | |`S` |Short - erased to I at runtime | 36 | |`Z` |Boolean - erased to I at runtime| 37 | |`F` |32-bit floating point | 38 | |`L` |64-bit integer (long) | 39 | |`D` |64-bit floating point (double) | 40 | 41 | The signature of `Object.equals` is `(Ljava/lang/Object;)Z` -- the method takes an Object and returns a boolean. 42 | This is baked into the method and it's caller (an invokevirtual operation). 43 | 44 | The power of a polymorphic signature is that there are no baked in argument types - you can pass any arguments you like and receive any return type you like. 45 | 46 | This is somewhat present already in Java - due to it's inheritance model any reference type can be expressed as a `java.lang.Object`, so surely you could create a method like this, which can receive any type and number of arguments and any type of return value: 47 | ```Java 48 | final native Object myMethod(Object... args); 49 | ``` 50 | Well, not quite. 51 | While this will allow every **reference** type, there are plenty of types that cannot be expressed as an Object -- think primitives and void. 52 | This definition would not allow you to pass an integer (at least not without boxing), and would not allow you to return `void`. 53 | 54 | ### How do we define a signature polymorphic method? 55 | 56 | [JVMS 2.9.3](https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-2.html#jvms-2.9.3) defines a method as being signature polymorphic if: 57 | 58 | * It is declared in the `java.lang.invoke.MethodHandle` class or the `java.lang.invoke.VarHandle` class. 59 | * It has a single formal parameter of type `Object[]`. 60 | * It has the `ACC_VARARGS` and `ACC_NATIVE` flags set. 61 | 62 | (Note: No requirement on the return type) 63 | 64 | This means a polymorphic function could be declared like so (in the right class): 65 | ```Java 66 | final native Object myMethod(Object... args); 67 | ``` 68 | 69 | The method's signature is identical to that of the variable argument method, so it is easy to question how they are functionaly different. 70 | The difference is that although the method is **declared** with `Object` parameters, it can in fact be called with any parameters, including primitive ones. 71 | 72 | **Side Note:** If you look at the source of the signature polymorphic `invokeExact` method you will notice it has a special annotation: 73 | ```Java 74 | public final native @PolymorphicSignature Object invokeExact(Object... args) throws Throwable; 75 | ``` 76 | When first writing this post I assumed that the way the JVM detects methods is with this annotation. 77 | Weirdly it is completely unrelated, and I guess just some source level way of highlighting it? 78 | Even that doesn't make much sense though since it is set to be retained at runtime. 79 | 80 | ### How do we use a signature polymorphic method? 81 | 82 | The best way to describe this is to show how they are compiled. 83 | I will use `MethodHandle.invokeExact` as an example of a signature polymorphic method, and a custom method to show a regular varargs invocation. 84 | ```Java 85 | public class Main { 86 | public static void main(String[] args) throws Throwable { 87 | var arr = new ArrayList(); 88 | // we dont actually care what method handle is, 89 | // we just want to see the outputted bytecode 90 | var mh = (MethodHandle) null; 91 | int out = (int) mh.invokeExact(arr); 92 | Integer boxed = (Integer) myPolymorphicMethod(arr); 93 | } 94 | 95 | // An attempt at a polymorphic method - doesn't really work 96 | private static Object myPolymorphicMethod(Object... args) { 97 | return null; 98 | } 99 | } 100 | ``` 101 | 102 | This is compiled to: 103 | ```Java 104 | public static void main(java.lang.String[]) throws java.lang.Throwable; 105 | descriptor: ([Ljava/lang/String;)V 106 | flags: (0x0009) ACC_PUBLIC, ACC_STATIC 107 | 108 | Code: 109 | stack=4, locals=5, args_size=1 110 | start local 0 // java.lang.String[] args 111 | 0: new #7 // class java/util/ArrayList 112 | 3: dup 113 | 4: invokespecial #9 // Method java/util/ArrayList."":()V 114 | 7: astore_1 115 | start local 1 // java.util.ArrayList arr 116 | 8: aconst_null 117 | 9: checkcast #10 // class java/lang/invoke/MethodHandle 118 | 12: astore_2 119 | start local 2 // java.lang.invoke.MethodHandle mh 120 | 13: aload_2 121 | 14: aload_1 122 | 15: invokevirtual #12 // Method java/lang/invoke/MethodHandle.invokeExact:(Ljava/util/ArrayList;)I 123 | 18: istore_3 124 | start local 3 // int out 125 | 19: iconst_1 126 | 20: anewarray #2 // class java/lang/Object 127 | 23: dup 128 | 24: iconst_0 129 | 25: aload_1 130 | 26: aastore 131 | 27: invokestatic #16 // Method myPolymorphicMethod:([Ljava/lang/Object;)Ljava/lang/Object; 132 | 30: checkcast #22 // class java/lang/Integer 133 | 33: astore 4 134 | start local 4 // java.lang.Integer boxed 135 | 35: return 136 | end local 4 // java.lang.Integer boxed 137 | end local 3 // int out 138 | end local 2 // java.lang.invoke.MethodHandle mh 139 | end local 1 // java.util.ArrayList arr 140 | end local 0 // java.lang.String[] args 141 | ``` 142 | 143 | When we call a varargs method such as `myPolymorphicMethod`, the methods descriptor is not *actually* polymorphic -- it only takes a single argument which is an Object array. 144 | The compiler simply boxes any arguments into the array which lets the method *effectively* take a variable number of arguments. 145 | Obviously this has an overhead. 146 | You can see by the generated bytecode this is avoided by the `PolymorphicSignature` method - the caller specifies whichever descriptor it wants and the JVM will allow it. 147 | 148 | Another benefit of this approach is the support for primitives: as you can see the `PolymorphicSignature` method is able to have primitives specified in it's descriptor. 149 | When interacting with `myPolymorphicMethod` any primitives must be boxed in order to be passed around as references. 150 | These dynamic descriptors even extend to `void`, allowing polymorphic descriptor methods to not return a value - something that would otherwise not be possible. 151 | 152 | The value with these methods is obvious: the ability to invoke methods with any arguments, without overhead of primitive boxing/unboxing or array creation. 153 | 154 | ## The benefits of Method Handles 155 | 156 | * **Signature Polymorphic** 157 | 158 | We already went through this one 159 | 160 | * **Smarter Access Controls** 161 | 162 | In reflection access controls are enforced upon invocation whereas with method handles the access checks are performed in order to obtain the method handle. This results in better performance - no need to continously check access, and also provides more flexibility, for example, obtaining a method handle for a private method and giving that method handle to code which would not normally be able to access that method. 163 | 164 | * **Flexibility** 165 | 166 | Method handles aren't just for invoking methods - their flexibility allows them to do things like getting/setting fields, constructing objects and even acting as intermediaries for an underlying method handle, for example, the `asSpreader` method[^1] which creates a method handle which calls an underlying method handle after spreading the input argument. 167 | 168 | 169 | ## Using Method Handles 170 | 171 | So let's say we want to invoke a method, known only at runtime, which can take any number or type of arguments, including primitive ones. 172 | 173 | The traditional way of doing this would be using reflection like so: 174 | ```Java 175 | var method = System.class.getDeclaredMethod("exit", int.class); 176 | method.invoke(null, 1); 177 | ``` 178 | 179 | But to avoid the overhead of boxing the integer argument we can use method handles: 180 | ```Java 181 | MethodHandles.Lookup lookup = MethodHandles.lookup(); 182 | var mh = lookup.findStatic(System.class, "exit", MethodType.methodType(void.class, int.class)); 183 | mh.invokeExact(1); 184 | ``` 185 | 186 | To use a method handle from bytecode see the dissasembly a posted earlier. 187 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution-ShareAlike 4.0 International Public License 2 | 3 | By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. 4 | 5 | Section 1 – Definitions. 6 | 7 | Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. 8 | Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. 9 | BY-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses, approved by Creative Commons as essentially the equivalent of this Public License. 10 | Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. 11 | Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. 12 | Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. 13 | License Elements means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution and ShareAlike. 14 | Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. 15 | Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. 16 | Licensor means the individual(s) or entity(ies) granting rights under this Public License. 17 | Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. 18 | Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. 19 | You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. 20 | 21 | Section 2 – Scope. 22 | 23 | License grant. 24 | Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: 25 | reproduce and Share the Licensed Material, in whole or in part; and 26 | produce, reproduce, and Share Adapted Material. 27 | Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 28 | Term. The term of this Public License is specified in Section 6(a). 29 | Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. 30 | Downstream recipients. 31 | Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. 32 | Additional offer from the Licensor – Adapted Material. Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter’s License You apply. 33 | No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 34 | No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). 35 | 36 | Other rights. 37 | Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 38 | Patent and trademark rights are not licensed under this Public License. 39 | To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. 40 | 41 | Section 3 – License Conditions. 42 | 43 | Your exercise of the Licensed Rights is expressly made subject to the following conditions. 44 | 45 | Attribution. 46 | 47 | If You Share the Licensed Material (including in modified form), You must: 48 | retain the following if it is supplied by the Licensor with the Licensed Material: 49 | identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); 50 | a copyright notice; 51 | a notice that refers to this Public License; 52 | a notice that refers to the disclaimer of warranties; 53 | a URI or hyperlink to the Licensed Material to the extent reasonably practicable; 54 | indicate if You modified the Licensed Material and retain an indication of any previous modifications; and 55 | indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 56 | You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 57 | If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 58 | ShareAlike. 59 | 60 | In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply. 61 | The Adapter’s License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-SA Compatible License. 62 | You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material. 63 | You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply. 64 | 65 | Section 4 – Sui Generis Database Rights. 66 | 67 | Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: 68 | 69 | for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; 70 | if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and 71 | You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. 72 | 73 | For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. 74 | 75 | Section 5 – Disclaimer of Warranties and Limitation of Liability. 76 | 77 | Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. 78 | To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. 79 | 80 | The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. 81 | 82 | Section 6 – Term and Termination. 83 | 84 | This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. 85 | 86 | Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 87 | automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 88 | upon express reinstatement by the Licensor. 89 | For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. 90 | For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. 91 | Sections 1, 5, 6, 7, and 8 survive termination of this Public License. 92 | 93 | Section 7 – Other Terms and Conditions. 94 | 95 | The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. 96 | Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. 97 | 98 | Section 8 – Interpretation. 99 | 100 | For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. 101 | To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. 102 | No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. 103 | Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blog 2 | 3 | Where I write stuff. 4 | 5 | Hosted at [blog.binclub.dev](https://blog.binclub.dev). 6 | 7 | ## Technical 8 | 9 | The blog is statically generated using [pandoc](https://pandoc.org) with some custom templates to convert the markdown blog files to html to be served. 10 | 11 | Since the blog is completely statically generated (even code highlighting and mathjax expressions), no JavaScript is required to view the blog. 12 | If JavaScript is present then a comment section can be loaded, but this is not necessary. 13 | 14 | ## Requirements 15 | 16 | * pandoc 17 | * nodejs 18 | * mathjax-node-cli (npm package) 19 | 20 | ## More Info 21 | 22 | A lot of these posts contain code samples. 23 | 24 | Some of them will rely upon [jasm](https://wiki.openjdk.java.net/display/CodeTools/asmtools) for assembling JVM class files. 25 | A precompiled binary is provided in the root directory of this project. 26 | To compile a .jasm file use `java -jar asmtools.jar jasm file.jasm`. 27 | 28 | For writing markdown with pandoc refer to . 29 | -------------------------------------------------------------------------------- /asmtools.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x4e/Blog/0b630c3e4bb52d5ded9595635aca346768e9504c/asmtools.jar -------------------------------------------------------------------------------- /filters/getpostdetails.lua: -------------------------------------------------------------------------------- 1 | local utils = require 'pandoc.utils' 2 | 3 | local stringify = function (el) 4 | if el then 5 | if type(el) == "table" then 6 | return el.t 7 | and utils.stringify(el) 8 | or utils.stringify(pandoc.Span(el)) 9 | end 10 | return el 11 | end 12 | return "" 13 | end 14 | 15 | local arrToStr = function(arr) 16 | if arr then 17 | str = "" 18 | for k, v in pairs(arr) do 19 | -- assumes that stringify(v) does not contain a "," 20 | str = str .. stringify(v) .. "," 21 | end 22 | -- Remove trailing comma 23 | if str ~= "" then 24 | str = str:sub(0, str:len()-1) 25 | end 26 | return str 27 | end 28 | return "[]" 29 | end 30 | 31 | function Meta(meta) 32 | print(stringify(meta.title)) 33 | print(stringify(meta.author)) 34 | print(arrToStr(meta.keywords)) 35 | print(stringify(meta.description)) 36 | print(stringify(meta.date)) 37 | print(arrToStr(meta.resources)) 38 | if meta.unlisted then 39 | print("true") 40 | else 41 | print("false") 42 | end 43 | os.exit(0) 44 | end 45 | -------------------------------------------------------------------------------- /filters/lineallcode.lua: -------------------------------------------------------------------------------- 1 | --- Adds lines to all code blocks even if not specified in the attributes 2 | 3 | function CodeBlock(el) 4 | table.insert(el.attr.classes, "numberLines") 5 | return el 6 | end 7 | -------------------------------------------------------------------------------- /filters/math2svg.lua: -------------------------------------------------------------------------------- 1 | -- VERSION 2021-01-19 2 | 3 | 4 | -- DESCRIPTION 5 | -- 6 | -- This Lua filter for Pandoc converts LaTeX math to MathJax generated 7 | -- scalable vector graphics (SVG) for insertion into the output document 8 | -- in a standalone manner. SVG output is in any of the available MathJax 9 | -- fonts. This is useful when a CSS paged media engine cannot process complex 10 | -- JavaScript. No Internet connection is required when generating or viewing 11 | -- SVG formulas, resulting in both absolute privacy and offline, standalone 12 | -- robustness. 13 | 14 | 15 | -- REQUIREMENTS, USAGE & PRIVACY 16 | -- 17 | -- See: https://github.com/pandoc/lua-filters/tree/master/math2svg 18 | 19 | 20 | -- LICENSE 21 | -- 22 | -- Copyright (c) 2020-2021 Serge Y. Stroobandt 23 | -- 24 | -- MIT License 25 | -- 26 | -- Permission is hereby granted, free of charge, to any person obtaining a 27 | -- copy of this software and associated documentation files (the "Software"), 28 | -- to deal in the Software without restriction, including without limitation 29 | -- the rights to use, copy, modify, merge, publish, distribute, sublicense, 30 | -- and/or sell copies of the Software, and to permit persons to whom the 31 | -- Software is furnished to do so, subject to the following conditions: 32 | -- 33 | -- The above copyright notice and this permission notice shall be included in 34 | -- all copies or substantial portions of the Software. 35 | -- 36 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 37 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 38 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 39 | -- THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 40 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 41 | -- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 42 | -- DEALINGS IN THE SOFTWARE. 43 | 44 | 45 | -- CONTACT 46 | -- 47 | -- $ echo c2VyZ2VAc3Ryb29iYW5kdC5jb20K |base64 -d 48 | 49 | 50 | -- The full path to the tex2svg binary of the mathjax-node-cli package. 51 | local tex2svg = 'tex2svg' 52 | 53 | -- By default, DisplayMath is converted to SVG, whereas InlineMath is not. 54 | local display2svg = true 55 | local inline2svg = true 56 | -- The fallback is MathML when pandoc is executed with the --mathml argument. 57 | 58 | -- Textual annotation for speech 59 | local speech = false 60 | 61 | -- Automatic line breaking 62 | local linebreaks = true 63 | 64 | -- MathJax font 65 | local font = 'TeX' 66 | -- Supported MathJax fonts are: 67 | -- https://docs.mathjax.org/en/latest/output/fonts.html 68 | 69 | -- ex unit size in pixels 70 | local ex = 6 71 | 72 | -- Container width in ex for line breaking and tags 73 | local width = 100 74 | 75 | -- String of MathJax extensions for TeX and LaTeX to be loaded at run time 76 | local extensions = '' 77 | -- Available MathJax extensions are listed in: 78 | -- /usr/local/lib/node_modules/mathjax-node-cli/node_modules/mathjax/unpacked/\ 79 | -- extensions/ 80 | 81 | -- Speed up conversion by caching SVG. 82 | local cache = true 83 | 84 | local _cache = {} 85 | _cache.DisplayMath = {} 86 | _cache.InlineMath = {} 87 | 88 | local tags = {} 89 | tags.DisplayMath = {'', ''} 90 | tags.InlineMath = {'', ''} 91 | 92 | 93 | function Meta(meta) 94 | 95 | tex2svg = tostring(meta.math2svg_tex2svg or tex2svg) 96 | display2svg = meta.math2svg_display2svg or display2svg 97 | inline2svg = meta.math2svg_inline2svg or inline2svg 98 | speech = tostring(meta.math2svg_speech or speech) 99 | linebreaks = tostring(meta.math2svg_linebreaks or linebreaks) 100 | font = tostring(meta.math2svg_font or font) 101 | ex = tostring(meta.math2svg_ex or ex) 102 | width = tostring(meta.math2svg_width or width) 103 | extensions = tostring(meta.math2svg_extensions or extensions) 104 | cache = tostring(meta.math2svg_cache or cache) 105 | 106 | end 107 | 108 | 109 | function Math(elem) 110 | 111 | local svg = nil 112 | 113 | local argumentlist = { 114 | '--speech', speech, 115 | '--linebreaks', linebreaks, 116 | '--font', font, 117 | '--ex', ex, 118 | '--width', width, 119 | '--extensions', extensions, 120 | elem.text 121 | } 122 | 123 | -- The available options for tex2svg are: 124 | --help Show help [boolean] 125 | --version Show version number [boolean] 126 | --inline process as in-line TeX [boolean] 127 | --speech include speech text [boolean] [default: true] 128 | --linebreaks perform automatic line-breaking [boolean] 129 | --font web font to use [default: "TeX"] 130 | --ex ex-size in pixels [default: 6] 131 | --width width of container in ex [default: 100] 132 | --extensions extra MathJax extensions [default: ""] 133 | -- e.g. 'Safe,TeX/noUndefined' 134 | 135 | if (elem.mathtype == 'DisplayMath' and display2svg) 136 | or (elem.mathtype == 'InlineMath' and inline2svg ) then 137 | 138 | if cache then 139 | -- Attempt to retrieve cache. 140 | svg = _cache[elem.mathtype][elem.text] 141 | end 142 | 143 | if not svg then 144 | 145 | if elem.mathtype == 'InlineMath' then 146 | -- Add the --inline argument to the argument list. 147 | table.insert(argumentlist, 1, '--inline') 148 | end 149 | 150 | -- Generate SVG. 151 | svg = pandoc.pipe(tex2svg, argumentlist, '') 152 | 153 | if cache then 154 | -- Add to cache. 155 | _cache[elem.mathtype][elem.text] = svg 156 | end 157 | 158 | end 159 | 160 | end 161 | 162 | -- Return 163 | if svg then 164 | 165 | if FORMAT:match '^html.?' then 166 | svg = tags[elem.mathtype][1] .. svg .. tags[elem.mathtype][2] 167 | return pandoc.RawInline(FORMAT, svg) 168 | 169 | else 170 | local filename = pandoc.sha1(svg) .. '.svg' 171 | pandoc.mediabag.insert(filename, 'image/svg+xml', svg) 172 | return pandoc.Image('', filename) 173 | 174 | end 175 | 176 | else 177 | 178 | return elem 179 | 180 | end 181 | 182 | end -- function 183 | 184 | 185 | -- Redefining the execution order. 186 | return { 187 | {Meta = Meta}, 188 | {Math = Math} 189 | } 190 | -------------------------------------------------------------------------------- /filters/wordcount.lua: -------------------------------------------------------------------------------- 1 | -- counts words in a document 2 | 3 | words = 0 4 | characters = 0 5 | characters_and_spaces = 0 6 | process_anyway = false 7 | 8 | wordcount = { 9 | Str = function(el) 10 | -- we don't count a word if it's entirely punctuation: 11 | if el.text:match("%P") then 12 | words = words + 1 13 | end 14 | characters = characters + utf8.len(el.text) 15 | characters_and_spaces = characters_and_spaces + utf8.len(el.text) 16 | end, 17 | 18 | Space = function(el) 19 | characters_and_spaces = characters_and_spaces + 1 20 | end, 21 | 22 | Code = function(el) 23 | _,n = el.text:gsub("%S+","") 24 | words = words + n 25 | text_nospace = el.text:gsub("%s", "") 26 | characters = characters + utf8.len(text_nospace) 27 | characters_and_spaces = characters_and_spaces + utf8.len(el.text) 28 | end, 29 | 30 | CodeBlock = function(el) 31 | _,n = el.text:gsub("%S+","") 32 | words = words + n 33 | text_nospace = el.text:gsub("%s", "") 34 | characters = characters + utf8.len(text_nospace) 35 | characters_and_spaces = characters_and_spaces + utf8.len(el.text) 36 | end 37 | } 38 | 39 | -- check if the `wordcount` variable is set to `process-anyway` 40 | function Meta(meta) 41 | if meta.wordcount and (meta.wordcount=="process-anyway" 42 | or meta.wordcount=="process" or meta.wordcount=="convert") then 43 | process_anyway = true 44 | end 45 | end 46 | 47 | function Pandoc(el) 48 | -- skip metadata, just count body: 49 | pandoc.walk_block(pandoc.Div(el.blocks), wordcount) 50 | print(words)-- .. " words in body") 51 | --print(characters .. " characters in body") 52 | --print(characters_and_spaces .. " characters in body (including spaces)") 53 | if not process_anyway then 54 | os.exit(0) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /make.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | import urllib 5 | import atexit 6 | import subprocess 7 | from pathlib import Path 8 | from datetime import datetime, date 9 | from dataclasses import dataclass 10 | from distutils.dir_util import mkpath, copy_tree, remove_tree 11 | 12 | # Execute the given command 13 | def execute(args, input=None): 14 | if isinstance(args, str): 15 | args = args.split(" ") 16 | 17 | res = subprocess.run(args, input=input, text=True, capture_output=True) 18 | 19 | if res.returncode != 0: 20 | print("CMD:") 21 | cmdStr = "\"" 22 | for arg in args: 23 | cmdStr += arg + "\" \"" 24 | cmdStr = cmdStr[:-2] 25 | print(cmdStr) 26 | print("STDOUT:") 27 | print(res.stdout) 28 | print("STDERR:") 29 | print(res.stderr) 30 | raise Exception("Running command " + str(args) + " encountered code " + str(res.returncode)) 31 | 32 | return res.stdout 33 | 34 | def parseLuaArr(text): 35 | if not text or text == "[]": 36 | return [] 37 | else: 38 | return text.split(",") 39 | 40 | @dataclass(frozen=True, repr=False) 41 | class Post(object): 42 | path: str 43 | time: str 44 | title: str 45 | author: str 46 | keywords: frozenset 47 | description: str 48 | dateStr: str 49 | date: datetime 50 | content: str 51 | resources: frozenset 52 | unlisted: bool 53 | 54 | def __repr__(self): 55 | return self.title 56 | 57 | 58 | # A list of files to be deleted when the program executes 59 | removeOnExit = [] 60 | def deleteRemoveOnExists(): 61 | for remove in removeOnExit: 62 | if remove.exists(): 63 | if remove.is_dir(): 64 | remove_tree(remove) 65 | else: 66 | remove.unlink() 67 | removeOnExit.clear() 68 | atexit.register(deleteRemoveOnExists) 69 | 70 | 71 | DATETIME_FORMAT = "%Y-%m-%dT%T%zZ" 72 | 73 | pandoc = "pandoc" 74 | # Check if there is a pandoc binary in the current folder 75 | if Path("./pandoc").exists(): 76 | pandoc = "./pandoc" 77 | # Also allow overriding by env variables 78 | if "PANDOC" in os.environ: 79 | pandoc = os.environ["PANDOC"] 80 | 81 | rootPath = Path(".").resolve() 82 | rootStr = str(rootPath) 83 | outPath = Path("out").resolve() 84 | resourcePath = Path("resources").resolve() 85 | tagPath = Path(outPath, "tag").resolve() 86 | 87 | if outPath.exists(): 88 | remove_tree(outPath) 89 | outPath.mkdir() 90 | 91 | 92 | # Create a symlink in the out folder to the resources 93 | Path(outPath, "resources").symlink_to(resourcePath, target_is_directory=True) 94 | 95 | 96 | templates = ["index", "posts", "post", "tag", "404"] 97 | mainTemplate = Path(rootPath, "templates/maintemplate.html").read_text() 98 | for template in templates: 99 | origPath = Path(rootPath, "templates/{}.html".format(template)) 100 | tmpPath = Path(rootPath, "templates/{}_tmp.html".format(template)) 101 | removeOnExit.append(tmpPath) 102 | tmpPath.write_text(mainTemplate.replace("$BODY$", origPath.read_text())) 103 | 104 | 105 | # Gather details about all posts 106 | def gatherPosts(rootPath): 107 | # Array of all posts 108 | posts = [] 109 | # Map of tags to posts 110 | tags = {} 111 | 112 | # We assume every index.md file is a blog post 113 | for sourcePath in rootPath.glob("*/index.md"): 114 | sourceStr = str(sourcePath) 115 | print("Indexing", sourceStr) 116 | 117 | relativePath = sourceStr[len(rootStr):] 118 | 119 | # Use the lua filter to get post details from the yaml metadata 120 | (title, author, keywords, description, date, resources, unlisted) = \ 121 | execute([pandoc, "--lua-filter", "filters/getpostdetails.lua", sourceStr]).strip().split("\n") 122 | keywords = frozenset(parseLuaArr(keywords)) 123 | resources = frozenset(parseLuaArr(resources)) 124 | dateStr = date 125 | date = date.replace("th ", " ").replace("st ", " ").replace("rd ", " ") 126 | date = datetime.strptime(date, "%d %B %Y") 127 | unlisted = unlisted == "true" 128 | 129 | wordCount = int(execute([pandoc, "--lua-filter", "filters/wordcount.lua", sourceStr])) 130 | timeToRead = wordCount // 220 # estimate 220 wpm reading speed 131 | 132 | content = sourcePath.read_text() 133 | content = content[content.index("---", 3) + 3:].strip() # trim the header from the content 134 | 135 | postDetails = Post( 136 | path= relativePath[:-8], # remove /index.md to get relative directory 137 | time= str(timeToRead) + " mins", 138 | title= title, 139 | author= author, 140 | keywords= keywords, 141 | description= description, 142 | dateStr= dateStr, 143 | date= date, 144 | content= content, 145 | resources= resources, 146 | unlisted= unlisted 147 | ) 148 | 149 | posts.append(postDetails) 150 | 151 | # Create symlinks to the resources 152 | if resources: 153 | # Remove the beginning "/" as this will resolve as root on a filesystem 154 | relPath = postDetails.path[1:] 155 | targetPath = Path(outPath, relPath) 156 | mkpath(str(targetPath.parent.resolve())) 157 | mkpath(str(targetPath.resolve())) 158 | 159 | for resource in resources: 160 | resPath = Path(sourcePath.parent, resource) 161 | resSymPath = Path(targetPath, resource) 162 | resSymPath.symlink_to(resPath, target_is_directory=False) 163 | 164 | # Sort posts based on the creation date 165 | posts = sorted(posts, key=lambda item: item.date, reverse=True) 166 | 167 | for post in posts: 168 | if post.unlisted: 169 | continue 170 | 171 | for keyword in post.keywords: 172 | if keyword not in tags: 173 | tags[keyword] = [post] 174 | else: 175 | tags[keyword].append(post) 176 | 177 | # Sort tags based on the number of posts that use them 178 | tags = dict(sorted(tags.items(), key=lambda item: len(item[1]), reverse=True)) 179 | 180 | return (posts, tags) 181 | 182 | # Compile a markdown file at sourcePath 183 | def compileMarkdown(sourcePath, allTagsHtml): 184 | sourceStr = str(sourcePath) 185 | # Get absolute source path, remove prefix of the rootDir, then swap extension from md to html 186 | targetPath = Path(outPath, str(sourcePath.resolve())[len(rootStr) + 1:-3] + ".html") 187 | targetStr = str(targetPath) 188 | 189 | mkpath(str(targetPath.parent.resolve())) 190 | 191 | print("Compiling", sourceStr, "to", targetStr) 192 | args = [ 193 | pandoc, 194 | sourceStr, 195 | "-o", targetStr, 196 | "-f", "commonmark_x+yaml_metadata_block", 197 | "-V", "TAGS={}".format(allTagsHtml), 198 | "--standalone", 199 | "--highlight-style", "espresso", 200 | "--email-obfuscation", "references", 201 | "--indented-code-classes", "numberLines", 202 | "--mathml", "--lua-filter", "filters/math2svg.lua", 203 | "--template", "templates/post_tmp.html", 204 | "--css", "/resources/styles.css", 205 | "--css", "/resources/post.css", 206 | "--lua-filter", "filters/lineallcode.lua", 207 | ] 208 | execute(args) 209 | 210 | # First gather information about all the posts 211 | posts, tags = gatherPosts(rootPath) 212 | 213 | 214 | # Generate html representation of every tag 215 | print("Generating tag links") 216 | def tagToLink(tag): 217 | encoded = urllib.parse.quote(tag, safe='/') 218 | return "{}".format(encoded, tag) 219 | tagsToHtml = {tag: "{} ({})".format(tagToLink(tag), len(tags[tag])) for tag in tags.keys()} 220 | allTagsHtml = ", ".join(tagsToHtml.values()) 221 | 222 | # Generate html representation of every post 223 | def postToHtml(post): 224 | # encode any non ascii chars 225 | encodedTitle = post.title.encode('ascii', 'xmlcharrefreplace').decode() 226 | encodedDesc = post.description.encode('ascii', 'xmlcharrefreplace').decode() 227 | encodedPath = urllib.parse.quote(post.path, safe='/') 228 | tags = ", ".join(map(tagToLink, post.keywords)) 229 | return """ 230 |
231 |

232 | {} 233 |

234 | - 235 |

{}

236 |
237 | """.format(encodedPath, encodedTitle, post.dateStr, tags, encodedDesc) 238 | postsToHtml = {post: postToHtml(post) for post in posts if not post.unlisted} 239 | allPostsHtml = "\n".join(postsToHtml.values()) 240 | 241 | 242 | def compileTemplate(templateName, css=[], title="x4e's website"): 243 | templatePath = Path(rootPath, "templates/{}_tmp.html".format(templateName)).resolve() 244 | if templateName == "index": 245 | templateOut = Path(outPath, "{}.html".format(templateName)).resolve() 246 | else: 247 | templateOut = Path(outPath, "{}/index.html".format(templateName)).resolve() 248 | mkpath(str(templateOut.parent)) 249 | 250 | print("Compiling", str(templatePath)) 251 | 252 | command = [ 253 | pandoc, 254 | "-o", str(templateOut), 255 | "-V", "TAGS={}".format(allTagsHtml), 256 | "-V", "POSTS={}".format(allPostsHtml), 257 | "-V", "pagetitle={}".format(title), 258 | "-V", "description=Posts about reverse engineering", 259 | "-V", "keywords=x4e, blog, reverse engineering", 260 | "--standalone", 261 | "--highlight-style", "espresso", 262 | "--email-obfuscation", "references", 263 | "--indented-code-classes", "numberLines", 264 | "--template", str(templatePath), 265 | "--css", "/resources/styles.css", 266 | ] 267 | 268 | for sheet in css: 269 | command.append("--css") 270 | command.append("/resources/{}.css".format(sheet)) 271 | 272 | execute(command, input="") 273 | 274 | 275 | # index.html 276 | compileTemplate("index", ["index"]) 277 | 278 | # 404.html 279 | compileTemplate("404", title="404 Not Found") 280 | 281 | # posts.html 282 | compileTemplate("posts", title="x4e's blog posts") 283 | 284 | # Compile all markdown files 285 | for file in rootPath.glob("*/*.md"): 286 | compileMarkdown(file, allTagsHtml) 287 | 288 | print("Compiling tags") 289 | # Create pages for each tag 290 | for (tag, tagPosts) in tags.items(): 291 | postsHtml = "\n".join(map(lambda post: postsToHtml[post], tagPosts)) 292 | tagOutPath = Path(tagPath, "{}/index.html".format(tag)).resolve() 293 | mkpath(str(tagOutPath.parent)) 294 | execute([ 295 | pandoc, 296 | "-o", str(tagOutPath), 297 | "-V", "TAGS={}".format(allTagsHtml), 298 | "-V", "TAG={}".format(tag), 299 | "-V", "POSTS={}".format(postsHtml), 300 | "-V", "pagetitle=Posts tagged with {}".format(tag), 301 | "-V", "description=All posts with {} tag".format(tag), 302 | "-V", "keywords=x4e, blog, reverse engineering, {}".format(tag), 303 | "--standalone", 304 | "--highlight-style", "espresso", 305 | "--email-obfuscation", "references", 306 | "--indented-code-classes", "numberLines", 307 | "--template", "templates/tag_tmp.html", 308 | "--css", "/resources/styles.css", 309 | "--css", "/resources/index.css", 310 | ], input="") 311 | 312 | from xml.dom import minidom 313 | 314 | def createText(document, elementName, text, **kwargs): 315 | out = document.createElement(elementName) 316 | if text != None: 317 | out.appendChild(document.createTextNode(text)) 318 | for key in kwargs: 319 | out.setAttribute(key, kwargs[key]) 320 | return out 321 | 322 | xmlOut = Path(outPath, "feed.xml").resolve() 323 | print("Compiling", xmlOut) 324 | xml = minidom.Document() 325 | atom = createText(xml, "feed", None, xmlns="http://www.w3.org/2005/Atom") 326 | atom.xmlns = "http://www.w3.org/2005/Atom" 327 | 328 | atom.appendChild(createText(xml, "id", "https://blog.binclub.dev/")) 329 | atom.appendChild(createText(xml, "title", "x4e's blog")) 330 | atom.appendChild(createText(xml, "subtitle", "Posts about reverse engineering")) 331 | atom.appendChild(createText(xml, "updated", date.today().strftime(DATETIME_FORMAT))) 332 | atom.appendChild(createText(xml, "link", None, href="https://blog.binclub.dev/", rel="alternate")) 333 | atom.appendChild(createText(xml, "link", None, href="https://blog.binclub.dev/feed.xml", rel="self")) 334 | atom.appendChild(createText(xml, "category", None, term="x4e")) 335 | atom.appendChild(createText(xml, "category", None, term="blog")) 336 | atom.appendChild(createText(xml, "category", None, term="reverse engineering")) 337 | 338 | author = xml.createElement("author") 339 | author.appendChild(createText(xml, "name", "x4e")) 340 | author.appendChild(createText(xml, "email", "x4e_x4e@protonmail.com")) 341 | author.appendChild(createText(xml, "uri", "https://blog.binclub.dev/")) 342 | atom.appendChild(author) 343 | 344 | for post in posts: 345 | uri = "https://blog.binclub.dev{}".format(post.path) 346 | item = xml.createElement("entry") 347 | item.appendChild(createText(xml, "id", uri)) 348 | item.appendChild(createText(xml, "title", post.title)) 349 | item.appendChild(createText(xml, "published", post.date.strftime(DATETIME_FORMAT))) 350 | item.appendChild(createText(xml, "updated", post.date.strftime(DATETIME_FORMAT))) 351 | item.appendChild(createText(xml, "summary", post.description)) 352 | item.appendChild(createText(xml, "link", None, href=uri, rel="alternate")) 353 | for keyword in post.keywords: 354 | item.appendChild(createText(xml, "category", None, term=keyword)) 355 | atom.appendChild(item) 356 | 357 | content = post.content 358 | content.replace("\n", "
\n") 359 | cdata = xml.createCDATASection(content) 360 | contentNode = createText(xml, "content", None, type="text/markdown", src=uri) 361 | contentNode.appendChild(cdata) 362 | item.appendChild(contentNode) 363 | 364 | xml.appendChild(atom) 365 | xmlOut.write_text(xml.toprettyxml(indent ="\t")) 366 | -------------------------------------------------------------------------------- /resources/CC-BY-SA_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | ]> 3 | 4 | Creative Commons “Attribution-Share Alike” license icon 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /resources/index.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x4e/Blog/0b630c3e4bb52d5ded9595635aca346768e9504c/resources/index.css -------------------------------------------------------------------------------- /resources/post.css: -------------------------------------------------------------------------------- 1 | 2 | figure { 3 | display: grid; 4 | grid-template-columns: 1fr; 5 | } 6 | figcaption { 7 | text-align: center; 8 | } 9 | img, embed { 10 | max-width: 100%; 11 | max-height: 70vh; 12 | margin: 0 auto; 13 | } 14 | 15 | blockquote { 16 | margin: 1em 0 1em 1.7em; 17 | padding-left: 1em; 18 | border-left: 2px solid var(--main); 19 | color: var(--main2); 20 | } 21 | code { 22 | font-family: Menlo, Monaco, "Lucida Console", Consolas, monospace; 23 | font-size: 85% !important; 24 | margin: 0; 25 | } 26 | pre { 27 | margin: 1em 0; 28 | overflow: auto; 29 | } 30 | pre code { 31 | padding: 0; 32 | overflow: visible; 33 | } 34 | .sourceCode { 35 | background-color: transparent; 36 | overflow: visible; 37 | } 38 | hr { 39 | background-color: var(--main); 40 | border: none; 41 | height: 1px; 42 | } 43 | table caption { 44 | margin-bottom: 0.75em; 45 | } 46 | div#body > header { 47 | margin-bottom: 4em; 48 | text-align: center; 49 | } 50 | #TOC li { 51 | list-style: none; 52 | } 53 | #TOC a:not(:hover) { 54 | text-decoration: none; 55 | } 56 | code { 57 | white-space: pre-wrap; 58 | } 59 | span.smallcaps { 60 | font-variant: small-caps; 61 | } 62 | span.underline { 63 | text-decoration: underline; 64 | } 65 | div.column { 66 | display: inline-block; 67 | vertical-align: top; 68 | width: 50%; 69 | } 70 | div.hanging-indent { 71 | margin-left: 1.5em; 72 | text-indent: -1.5em; 73 | } 74 | ul.task-list { 75 | list-style: none; 76 | } 77 | pre > code.sourceCode { 78 | white-space: pre-line; 79 | position: relative; 80 | } 81 | pre > code.sourceCode > span { 82 | display: inline-block; 83 | line-height: 1.25; 84 | white-space: pre; 85 | } 86 | pre > code.sourceCode > span:empty { 87 | height: 1.2em; 88 | } 89 | .sourceCode { 90 | overflow: visible; 91 | } 92 | code.sourceCode > span { 93 | color: inherit; 94 | text-decoration: inherit; 95 | } 96 | pre.sourceCode { 97 | margin: 0; 98 | } 99 | @media screen { 100 | div.sourceCode { 101 | overflow: auto; 102 | } 103 | } 104 | @media print { 105 | pre > code.sourceCode { 106 | white-space: pre-wrap; 107 | } 108 | pre > code.sourceCode > span { 109 | text-indent: -5em; 110 | padding-left: 5em; 111 | } 112 | } 113 | pre.numberSource code { 114 | counter-reset: source-line 0; 115 | } 116 | pre.numberSource code > span { 117 | position: relative; 118 | counter-increment: source-line; 119 | } 120 | pre.numberSource code > span:target { 121 | background-color: var(--background3); 122 | } 123 | pre.numberSource code > span > a:first-child::before { 124 | content: counter(source-line); 125 | position: relative; 126 | left: -1em; 127 | text-align: right; 128 | vertical-align: baseline; 129 | border: none; 130 | display: inline-block; 131 | -webkit-touch-callout: none; 132 | -webkit-user-select: none; 133 | -khtml-user-select: none; 134 | -moz-user-select: none; 135 | -ms-user-select: none; 136 | user-select: none; 137 | padding: 0; 138 | width: 4em; 139 | color: var(--main); 140 | text-decoration: initial !important; 141 | } 142 | pre.numberSource { 143 | margin-left: 0; 144 | border-left: 1px solid var(--background2); 145 | } 146 | div.sourceCode { 147 | background-color: var(--background2); 148 | color: var(--main); 149 | } 150 | @media screen { 151 | pre > code.sourceCode > span > a:first-child::before { 152 | text-decoration: underline; 153 | } 154 | } 155 | code span.al { 156 | color: #ffff00; 157 | } /* Alert */ 158 | code span.an { 159 | color: #0066ff; 160 | font-weight: bold; 161 | font-style: italic; 162 | } /* Annotation */ 163 | code span.at { 164 | } /* Attribute */ 165 | code span.bn { 166 | color: #44aa43; 167 | } /* BaseN */ 168 | code span.bu { 169 | color: #43a8ed; 170 | } /* BuiltIn */ 171 | code span.cf { 172 | color: #43a8ed; 173 | font-weight: bold; 174 | } /* ControlFlow */ 175 | code span.ch { 176 | color: #049b0a; 177 | } /* Char */ 178 | code span.cn { 179 | } /* Constant */ 180 | code span.co { 181 | color: #0066ff; 182 | font-weight: bold; 183 | font-style: italic; 184 | } /* Comment */ 185 | code span.do { 186 | color: #0066ff; 187 | font-style: italic; 188 | } /* Documentation */ 189 | code span.dt { 190 | color: #43a8ed; 191 | } /* DataType */ 192 | code span.dv { 193 | color: #44aa43; 194 | } /* DecVal */ 195 | code span.er { 196 | color: #ffff00; 197 | font-weight: bold; 198 | } /* Error */ 199 | code span.ex { 200 | } /* Extension */ 201 | code span.fl { 202 | color: #44aa43; 203 | } /* Float */ 204 | code span.fu { 205 | color: #ff9358; 206 | font-weight: bold; 207 | } /* Function */ 208 | code span.im { 209 | } /* Import */ 210 | code span.in { 211 | color: #0066ff; 212 | font-weight: bold; 213 | font-style: italic; 214 | } /* Information */ 215 | code span.kw { 216 | color: #43a8ed; 217 | font-weight: bold; 218 | } /* Keyword */ 219 | code span.op { 220 | } /* Operator */ 221 | code span.pp { 222 | font-weight: bold; 223 | } /* Preprocessor */ 224 | code span.sc { 225 | color: #049b0a; 226 | } /* SpecialChar */ 227 | code span.ss { 228 | color: #049b0a; 229 | } /* SpecialString */ 230 | code span.st { 231 | color: #049b0a; 232 | } /* String */ 233 | code span.va { 234 | } /* Variable */ 235 | code span.vs { 236 | color: #049b0a; 237 | } /* VerbatimString */ 238 | code span.wa { 239 | color: #ffff00; 240 | font-weight: bold; 241 | } /* Warning */ 242 | .display.math { 243 | display: block; 244 | text-align: center; 245 | margin: 0.5rem auto; 246 | } 247 | -------------------------------------------------------------------------------- /resources/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --accent: #4ba6e1; 3 | --main: #e9e9f0; 4 | --main2: #cecece; 5 | --main3: #b1b1b1; 6 | --background: #232931; 7 | --background2: #393e46; 8 | --background3: #435168; 9 | --alt1: #004466; 10 | --alt2: #C9AF9D; 11 | --alt3: #04bb0f; 12 | --alt4: #F2603C; 13 | } 14 | 15 | * { 16 | scrollbar-color: #454a4d #202324; 17 | } 18 | 19 | html { 20 | color: var(--main); 21 | background-color: var(--background); 22 | font-family: sans-serif, serif; 23 | } 24 | 25 | body { 26 | max-width: 100vw; 27 | } 28 | 29 | body > * { 30 | max-width: calc(100vw - 8px); 31 | } 32 | 33 | a { 34 | color: var(--alt4); 35 | text-decoration: none; 36 | } 37 | a:visited { 38 | color: var(--alt4); 39 | } 40 | 41 | table,td,th { 42 | border-color: var(--main3) !important; 43 | } 44 | 45 | body > .title,h1,h2,h3,h4,h5,h6,a,p,ol,ul,hr,div,header#title-block-header,section { 46 | box-sizing: border-box; 47 | width: 900px; 48 | max-width: calc(100vw - (8px * 2)); 49 | margin: 0 auto; 50 | margin-top: revert; 51 | margin-bottom: revert; 52 | overflow-wrap: break-word; 53 | } 54 | 55 | body > div#post-content > ol,ul,li { 56 | max-width: calc(100vw - (8px * 2) - 40px); 57 | } 58 | 59 | body > div#post-content > ol,ul,li > p { 60 | max-width: 100%; 61 | } 62 | 63 | li { 64 | text-align: initial !important; 65 | } 66 | 67 | header#main-header { 68 | display: grid; 69 | 70 | margin: 0 auto; 71 | width: 900px; 72 | max-width: 100%; 73 | margin-bottom: 20px; 74 | } 75 | 76 | header#main-header > div { 77 | text-align: center; 78 | } 79 | 80 | header#main-header > a { 81 | width: auto; 82 | } 83 | 84 | header#main-header > * { 85 | margin: auto; 86 | } 87 | 88 | header#title-block-header > * { 89 | margin: auto; 90 | text-align: center; 91 | } 92 | 93 | body > p,li { 94 | text-align: justify; 95 | } 96 | 97 | body > table { 98 | max-width: 1200px; 99 | margin: 0 auto; 100 | margin-top: revert; 101 | margin-bottom: revert; 102 | } 103 | 104 | table, th, td { 105 | border: 1px solid black; 106 | border-collapse: collapse; 107 | } 108 | 109 | th, td { 110 | padding: 0 15px; 111 | max-width: 400px; 112 | } 113 | 114 | div#body > div#posts > div#post > h4 { 115 | margin-bottom: 5px; 116 | } 117 | 118 | div#body > div#posts > div#post > p { 119 | margin-top: 5px; 120 | } 121 | -------------------------------------------------------------------------------- /templates/404.html: -------------------------------------------------------------------------------- 1 |

404 Not Found

2 |

3 | Could not find the page you requested. 4 |

5 |

6 | Return to homepage. 7 |

-------------------------------------------------------------------------------- /templates/emptytemplate.html: -------------------------------------------------------------------------------- 1 | $body$ 2 | $for(include-after)$ 3 | $include-after$ 4 | $endfor$ -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | home 4 | posts 5 |
6 |
7 | 8 |
9 |

x4e

10 |

I do reverse engineering, mostly JVM related.

11 |

At the moment I mainly work on the Binscure JVM Obfuscator.

12 | 17 | 29 |
30 | -------------------------------------------------------------------------------- /templates/maintemplate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | $for(author)$ 9 | 10 | $endfor$ 11 | $if(date-meta)$ 12 | 13 | $endif$ 14 | $if(keywords)$ 15 | 16 | $endif$ 17 | $if(description)$ 18 | 19 | $endif$ 20 | $if(title-prefix)$$title-prefix$ – $endif$$pagetitle$ 21 | $for(css)$ 22 | 23 | $endfor$ 24 | $if(math)$ 25 | $math$ 26 | $endif$ 27 | 28 | $for(header-includes)$ 29 | $header-includes$ 30 | $endfor$ 31 | 32 | 33 | 34 | $BODY$ 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /templates/post.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | home 4 | posts 5 |
6 |
7 | 8 | $for(include-before)$ 9 | $include-before$ 10 | $endfor$ 11 | 12 | $if(title)$ 13 |
14 |

$title$

15 | $if(subtitle)$ 16 |

$subtitle$

17 | $endif$ 18 | $if(keywords)$ 19 |

$for(keywords)$$keywords$$sep$, $endfor$

20 | $endif$ 21 | $if(date)$ 22 |

$date$

23 | $endif$ 24 |
25 | $endif$ 26 | 27 |
28 | $body$ 29 |
30 | 31 | $for(include-after)$ 32 | $include-after$ 33 | $endfor$ 34 | 35 |
36 | 37 |

You can post and view comments either below (if JavaScript is available) or at github.com/x4e/Blog/issues/.

38 | 39 | 46 | -------------------------------------------------------------------------------- /templates/posts.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | home 4 | posts 5 |
6 |
7 | 8 |
9 |

All Posts

10 | Atom Feed 11 |

Tags: $TAGS$

12 |
13 | $POSTS$ 14 |
15 |
16 | -------------------------------------------------------------------------------- /templates/tag.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | home 4 | posts 5 |
6 |
7 | 8 |
9 |

Posts tagged with $TAG$

10 |
11 | $POSTS$ 12 |
13 |
14 | --------------------------------------------------------------------------------