Дикая Java

«Безопасный язык» говорили они, «четкая спецификация» говорили они, «Java не даст вам выстрелить себе в ногу» и прочее в таком духе. Реальность же оказалась куда веселее официальной документации и мнений экспертов.

«JVM темна и полна ужасов». (ц)

«Hello world» для пожилых программистов.

Все началось как обычно:

копаясь в проектах на Github в поисках всякого интересного и необычного, наткнулся на подборку лютейшей дичи примеров особенного кода, с любовью отобранного неизвестным индийским студентом.

Часть из этих замечательных примеров оказалась реализована на. Java.

Что удивило, поскольку язык не славится undefined behavior и вообще редко используется для диких программистких трюков.

Так что я решил разобрать самые отбитые интересные примеры, благо в описаниях присутствовали ссылки на обсуждение конкретной «аномалии» и описание логики работы.

Вот как описывает подборку сам автор:

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 порадовало разрывом мозга типичных обитателей:

88ea1d653450ac63a9d7db77da719e0d.png

К счастью не все обитатели 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

Что тут происходит:

с помощью Reflection API — того самого универсального функционала, добавленного во имя добра и справедливости происходит нехорошее действо над другим универсальным фукнционалом — встроенного в JVM кеша для простых чисел.

Как известно добрыми намерениями выстелена дорога в Ад и пример выше — яркое тому подтверждение.

В результате работы этого кода, программист получает психологическую травму происходит незаметное изменение логики работы — вместо последовательного счетчика цикла отображается «отравленный» кеш с подмененным содержимым:

c735190c3899ddb38e818fc022f12ee6.png

Добавлю, что абсолютно точно не я предложил эту идею:

реализовать еще один метод, который бы возвращал кеш чисел в оригинальное состояние и затем вызывать оба метода из юнит-тестов, доводя тем самым произвольное количество разработчиков до дурки.

Пожалуйста не ссылайтесь на эту статью, когда будете такое реализовывать.

Дичь третья: прелести типизации

Прикинувшись рекрутером задам стандартный вопрос с собеседования — «объясните что делает этот код»:

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:

1b560a5a68dad0a19c6a0704f482a7d9.png

Ну разве Java не прелесть?

Дичь четвертая: максимальное веселье

Настоящий шедевр высокой кухни для истинных ценителей дичи, код после просмотра которого вы немедленно побежите прочесывать рабочие проекты в поисках подобных паттернов:

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
	 */
}

И да, это действительно работает:

94bbfb5869317f905a79dc4d02acff57.png

Теперь стоит рассказать как работает сей сказочный код.

Большой закомментированный блок с непонятными символами внутри, на самом деле вполне рабочий код на 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, написавшего в свое время достаточно известную книгу по Java:

Just Java™ 2, Fifth Edition

by Peter van der Linden

Released December 2001 Publisher(s): Pearson ISBN: 9780130320728

Но никаких упоминаний трюка с обфрускацией на его сайте найти не удалось.

Вторая ссылка на статью из блога Oracle оказалась битой и выдает 404, но ник автора (forax) навел на одного из Java Champion по имени Remi Forax.

Хотя его Github полон разных интересных проектов (некоторые из которых я со временем точно разберу), каких-либо упоминаний трюка с обфрускацией также найдено не было, увы.

Так что если кто из читателей сможет разыскать оригинал с разбором столь замечательного кода — обязательно добавлю в статью.

P.S.

Это цензурированная версия статьи, куда более вольный оригинал которой доступен в нашем блоге.

0×08 Software

Мы небольшая команда ветеранов ИТ‑индустрии, создаем и дорабатываем самое разнообразное программное обеспечение, наш софт автоматизирует бизнес‑процессы на трех континентах, в самых разных отраслях и условиях.

Оживляем давно умершее,  чиним никогда не работавшее и создаем невозможное — затем рассказываем об этом в своих статьях.

© Habrahabr.ru