Содомируя Java
«Безопасный язык» говорили они, «четкая спецификация» говорили они, «Java не даст вам выстрелить себе в ногу» и прочее и прочее.
Реальность же оказалась куда веселее официоза, так что пристегните ремни — мы начинаем погружение в мрачные грубины очередных отбитых технологий.
«Ибо JVM темна и полна ужасов».
копаясь в чужих проектах на Github в поисках всякого интересного, наткнулся на подборку лютейшей дичи — примеров трешевого кода, заботливо и с любовью отобранного неизвестным студентом-индусом.
Часть из этих примеров откровенной компьютерной содомии оказалась реализована на.. Java.
Что меня сильно удивило, поскольку язык не славился undefined behavior и редко используется для диких программистких трюков.
Так что я решил разобрать самые отбитые варианты на Java, благо были ссылки на обсуждение «аномалии» и описание логики работы.
Так и родилась эта замечательная статья.
Вот как описывает свою подборку оригинальный автор:
A Nonsense Collection of Disgusting Codes
Here we are talking about creepiest of the most creepy codes. Programs, behave so strange, that they will twist your brain. Snippets, so small, that you won’t believe their functionality. And codes, so cryptic, that even the top coders will think of going back to the college.
Never try this type of code in a real life software project; readability and maintainability should be the main concern there.
Видимо автор догадывается что ситуации бывают всякие, в том числе допускающие появление подобной дичи в реальных проектах.
Дичь первая: адский "Hello world"
Запуск этого примера вы можете наблюдать на заглавной картинке статьи, сам код выглядит вот так:
public class hello_world{ public static void main (String[] args) { for(long l=4946144450195624l; l>0; l>>=5) System.out.print((char) (((l & 31 | 64) % 95) + 32)); } }
Обсуждение на StackOverflow отдельно доставляет:
В двух комментариях выше отлично раскрыта вся соевая суть и современное состояние платформы.
К счастью еще не все обитатели SO окончательно отупели, поэтому очень быстро появилось адекватное объяснение происходящего:
You are getting a result which happens to be char
representation of below values
А приложенный фрагмент окончательно раскрывает все карты этого загадочного кода:
104 -> h 101 -> e 108 -> l 108 -> l 111 -> o 32 -> (space) 119 -> w 111 -> o 114 -> r 108 -> l 100 -> d
Думаю погружаться глубже и объяснять детали уже не надо — и так все понятно.
Дичь вторая: магическое кеширование
Оригинальная статья автора находится тут.
Код ниже — отличный пример side-effect, когда сначала во имя добра добавляют в проект некий универсальный функционал, а затем бегают кругами, кусают локти и вырывают последние остатки волос на жопе, в безуспешных попытках обуздать полет мысли конечных пользователей:
import java.lang.reflect.Field; import java.util.Random; public class crazy_jvm { public static void main(String[] args) throws Exception { justKidding(); for(int i=0; i<10; i++){ System.out.println((Integer) i); } } private static void justKidding() throws Exception{ // extract the IntegerCache through reflection Field field = Class.forName("java.lang.Integer$IntegerCache").getDeclaredField("cache"); field.setAccessible(true); Integer[] cache = (Integer[]) field.get("java.lang.Integer$IntegerCache"); // rewrite the Integer cache for (int i=0; i<cache.length; i++){ cache[i] = new Integer(new Random().nextInt()); } } }
с помощью Reflection API — того самого универсального функционала, добавленного во имя добра и справедливости происходит изнасилование другого универсального инструмента, также добавленного во имя добра — встроенного в JVM кэша для простых чисел.
Как известно добрыми намерениями выстелена дорога в Ад и код выше — яркое тому подтверждение.
В результате работы этого примера, программист получает психологическую травму происходит незаметное изменение логики работы — вместо последовательного счетчика цикла отображается отравленный кэш:
Дичь третья: прелести типизации
Сейчас я прикинусь типичным тупым рекрутером и задам стандартный вопрос с собеседований — «объясните что делает этот код»:
class confusion{ public static void main (String[] args){ int i = (byte) + (char) - (int) + (long) - 1; System.out.println(i); } }
Врядли, по крайней мере ни я сам ни кто-либо из моих коллег с ходу разобраться не смогли, хотя пишем на Java уже второй десяток лет.
Поэтому обратимся к подсказке от автора:
So this is just a load of casts applied to unary operators applied ultimately to the 1 at the end of the line. i.e. you are casting the -1 to a long, applying the + unary operator to it (so still -1), casting as an int, applying the unary - to it (we now have a value of 1), casting as a char, applying unary + to it and finally casting as a byte before storing as (and implicitly casting to) an int. It is simply the presence of the two unary minuses that makes the result 1.
Вот тут на StackOverflow находится более развернутое обсуждение.
Да, этот код действительно работает и отображает цифру 1:
Дичь четвертая: самый полный "Пэ"
Настоящий шедевр из мира трэша, блюдо высокой кухни для истинных ценителей дичи и код после просмотра которого вы немедленно подорветесь прочесывать рабочие проекты в поисках подобных паттернов:
public class obfuscated{ /** * Hi! * \u002a\u002f\u0020\u0070\u0075\u0062\u006c\u0069\u0063\u0020\u0020\u0020 \u0073\u0074\u0061\u0074\u0069\u0063\u0020\u0076\u006f\u0069\u0064\u0020 \u006d\u0061\u0069\u006e\u0028\u0053\u0074\u0072\u0069\u006e\u0067\u0020 \u005b\u005d\u0061\u0029\u007b\u0053\u0079\u0073\u0074\u0065\u006d\u002e \u006f\u0075\u0074\u002e\u0070\u0072\u0069\u006e\u0074\u006c\u006e\u0020 \u0028\u0022\u0048\u0069\u0021\u0022\u0029\u003b\u007d\u0020\u002f\u002a */ }
И да, это действительно работает:
Ну разве программирование это не весело?
Рассказываю как вся эта сказочная #банина работает.
Большой закомментированный блок с непонятными символами внутри, на самом деле вполне рабочий код на Java, просто перекодированный в UTF-16 нотацию.
После перекодирования обратно в ANSII он выглядит так:
*/ public static void main(String []a){System. out.println ("Hi!");} /*
Видите закрывающий и открывающий символы комментирования?
Именно этими символами и обеспечивается вся работа, весь трюк.
Стоит пояснять, что точно таким способом можно легко закодировать в рабочий проект например реверс-шелл?
К сожалению мне не удалось отыскать оригинал этой статьи, комментарий в шапке примера:
// Developer- Peter van der Linden (April 1, 1996), little modified. // Intro- Prints "Hi!" in the console, looks like a big meaningless comment though. // Details- https://community.oracle.com/blogs/forax/2006/10/16/obfuscated-java
дал имя автора Peter van der Linden, написавшего очень известную (в свое время) книгу:
Just Java™ 2, Fifth Edition
Но никаких упоминаний трюка с обфрускацией найти не удалось.
Еще Питер не очень любит русских, поэтому его домашняя страница закрыта от посетителей из РФ, пойдете смотреть — не забудьте включить VPN и прикинуться честным американцем.
Вторая ссылка на статью из блога Oracle оказалась битой и выдает 404, но ник автора (forax) навел на одного из Java Champion по имени Remi Forax.
Его Github доставляет и некоторые из проектов я со временем точно разберу, но каких-либо упоминаний трюка с обфрускацией я также не нашел, увы.