эксперт в майнкрафт апи
-
Автор темы
- #1
Всем привет!
В данном туториале вы найдете немного полезной информации на счет защиты своего чита от кряка.
Данный метод защиты используют такие читы как Akrien, люди(?нет, не люди.обычные пастеры чей уровень не заходит даже за видосы хаудихо.самые кринжовые клоуны) которые сидят на протекшене от аргентоза.
* Данный метод защиты - полный калл.Он спасает лишь потому, что майнкрафт комьюнити - абсолютно примитивные Java школокодеры.Иметь какие либо большие знания для создания данной защиты не надо.
* Данные защиты абсолютно идентичны.Отличаются лишь версии OpenJDK, pad`ы(= измененные оффсеты) которые добавляют создатели данных "грандиозных" защит.
Ну что ж, теперь к описанию принципа работы данной защиты.
У Java класса есть формат. (
Все что нам нужно сделать - изменить что либо в этом формате и собрать нашу Java после этого.
Исходя из выше написанного, нам также нужно сделать парсер класса из байтов, и реврайт по нашим новым правилам.
Начнем со сборки OpenJDK.
Переходим на
У них есть building.md/html, но я все же напишу краткий гайд по сборке.
Все что нам нужно :
1) Cygwin (
2) BootJDK (да, для сборки JDK, нам понадобится другая JDK) (
Итак, мы получили setup-x86_64.exe.
В этой же директории открываем cmd, пишем
setup-x86_64.exe -q -P autoconf -P make -P unzip -P zip
Теперь достанем исходники openjdk на диск C:/
В cygwin пишем :
bash configure --with-boot-jdk=bootjdk
make images
Это будет очень долго в первый раз.
Пока у меня идет сборка, расскажу вам что мы будем делать.
Формат класса состоит из :
CAFEBABE (первые 4 байта, "magic value" java класс файла).
Является отсылкой.(
Minor версия, Major версия класса. (2/2 байта)
Дальше идет парсинг содержимого класса.
1) констант пул.
2) супер класс, интерфейсы.
3) поля класса.
4) методы класса.
5) аттрибуты класса.
Из описанного выше вам может стать не совсем понятным лишь константпул.
Константпул - основа всего класса.В нем содержится самая основная информация - строки, int/float/long/double значения.
Основная задача идет на JVM_CONSTANT_Utf8
Например : спарсив JVM_CONSTANT_Utf8, он может спарсить и JVM_CONSTANT_Class :
// const u2 name_index = cfs->get_u2_fast();
// cp->klass_index_at_put(index, name_index);
Также и с остальными константами.Я надеюсь принцип понятен.
* Байткод методов также ссылается на индексы в константпуле и сам по себе какого либо значения не несет.
* Все что парсится в классе после константпула - лишь его индексы и размер того, что нужно спарсить.
* Для более подробного анализа вы можете скачать DirtyJOE
Все.java сбилжена.Но как нам что либо редактировать в ней?Лезть в notepad?Давайте это исправим.
make hotspot-ide-project
и ждем.
Все!
C:\openjdk\build\windows-x86_64-server-release\ide\hotspot-visualstudio
Что ж, половина задачи уже выполнена.
Нам нужен classFileParser.cpp.В нем и происходит основная магия - парсинг класса из байтов.
Заходим в метод parse_stream и видим :
Но что за u4, что за stream?
u4 - тайпдеф, который означает unsigned 4 byte.Либо же просто unsigned int.
В джаве их 4 вида :
u1, u2, u4, u8 (unsigned byte(C++ char), short, int, long long)
* Довольно смешным фактом является то, что джава сама по себе отрицает значения которые записываются меньше чем в 4 байта.
* Минимальный размер элемента стека - 4 байта.(локали также).wide значения(long,double) вписываются в 2 элемента.
* И в целом byte,boolean,short,char (1-2 byte) реализованы лишь инструкциями взаимодействия с массивами.(ex : SALOAD, SASTORE)(за обработку байтов и булеанок они вообще используют одну инструкцию - BASTORE/BALOAD.Ущемление прав, никак иначе)
stream - обычный класс-ридер потока байтов.имеет на борту методы аля read_u1/2/4/8, и текущий индекс чтения.
Почему все засрано какими то постоянными guarantee_more, assert, check_property?
Если вам тоже интересен этот вопрос как и мне, то ответ хранится в документации openjdk, а именно кодстайлу.
В двух словах, их главным принципом является лучше все проверить, чем поймать рандомный краш вмки.
Для нашей джавы конечно же желательно отказаться от такого дебага(бтв, assert останутся лишь в debug билде).По хорошему их всех удалить, либо покрыть xorstr.
Окей, теперь давайте рассинхронизируем наши задачи, а именно будем параллельно делать парсер, изменять значения, также изменяя их в джаве.
Создадим новый проект в IDE.
Что нам понадобится? Такой же ридер, врайтер.За основу возьмем DataInputStream, DataOutputStream.Он имеет на борту методы read(1 byte), readShort(2 bytes), readInt(4 bytes), readLong(8 bytes), аналогично с write.
В целом такой класс можно и не делать, а юзать DataInputStream, DataOutputStream, но тогда вы теряете возможность добавлять какой либо алгоритм при записи байтов.(ex : xor++ на каждый write изуродует stream при парсинге)
Я сделал для себя 2 класса :
ClassFileStream, ClassFileWriter
Подготовим парсер jar прямо в main методе :
Ну и теперь самое страшное...
Создадим класс ClassFile.
Поехали.
Вспоминаем что я писал выше.
первые четыре байта -> CAFEBABE.Проверим?
Круть!
Теперь перезапишем это, но в другом виде.Это будет маркером в парсере, что класс зашифрован.
Откроем hexed.it и посмотрим в out.jar -> Main.class
Продолжение написания "защиты" во втором туторе.Там мы перепишем parse_constant_pool_entries, соберем и протестим данное дерьмо.
Nursultan Dump :
В данном туториале вы найдете немного полезной информации на счет защиты своего чита от кряка.
Данный метод защиты используют такие читы как Akrien, люди(?нет, не люди.обычные пастеры чей уровень не заходит даже за видосы хаудихо.самые кринжовые клоуны) которые сидят на протекшене от аргентоза.
* Данный метод защиты - полный калл.Он спасает лишь потому, что майнкрафт комьюнити - абсолютно примитивные Java школокодеры.Иметь какие либо большие знания для создания данной защиты не надо.
* Данные защиты абсолютно идентичны.Отличаются лишь версии OpenJDK, pad`ы(= измененные оффсеты) которые добавляют создатели данных "грандиозных" защит.
Ну что ж, теперь к описанию принципа работы данной защиты.
У Java класса есть формат. (
Пожалуйста, авторизуйтесь для просмотра ссылки.
)Все что нам нужно сделать - изменить что либо в этом формате и собрать нашу Java после этого.
Исходя из выше написанного, нам также нужно сделать парсер класса из байтов, и реврайт по нашим новым правилам.
Начнем со сборки OpenJDK.
Переходим на
Пожалуйста, авторизуйтесь для просмотра ссылки.
.У них есть building.md/html, но я все же напишу краткий гайд по сборке.
Все что нам нужно :
1) Cygwin (
Пожалуйста, авторизуйтесь для просмотра ссылки.
)2) BootJDK (да, для сборки JDK, нам понадобится другая JDK) (
Пожалуйста, авторизуйтесь для просмотра ссылки.
)Итак, мы получили setup-x86_64.exe.
В этой же директории открываем cmd, пишем
setup-x86_64.exe -q -P autoconf -P make -P unzip -P zip
Теперь достанем исходники openjdk на диск C:/
Пожалуйста, авторизуйтесь для просмотра ссылки.
Теперь создадим папку для нашей bootjdk в данной директории и разархивируем туда то что мы качнули чуть выше.
Пожалуйста, авторизуйтесь для просмотра ссылки.
А теперь к сборке.В cygwin пишем :
bash configure --with-boot-jdk=bootjdk
make images
Это будет очень долго в первый раз.
Пока у меня идет сборка, расскажу вам что мы будем делать.
Формат класса состоит из :
CAFEBABE (первые 4 байта, "magic value" java класс файла).
Является отсылкой.(
Пожалуйста, авторизуйтесь для просмотра ссылки.
)Minor версия, Major версия класса. (2/2 байта)
Дальше идет парсинг содержимого класса.
1) констант пул.
2) супер класс, интерфейсы.
3) поля класса.
4) методы класса.
5) аттрибуты класса.
Из описанного выше вам может стать не совсем понятным лишь константпул.
Константпул - основа всего класса.В нем содержится самая основная информация - строки, int/float/long/double значения.
Основная задача идет на JVM_CONSTANT_Utf8
Например : спарсив JVM_CONSTANT_Utf8, он может спарсить и JVM_CONSTANT_Class :
// const u2 name_index = cfs->get_u2_fast();
// cp->klass_index_at_put(index, name_index);
Также и с остальными константами.Я надеюсь принцип понятен.
* Байткод методов также ссылается на индексы в константпуле и сам по себе какого либо значения не несет.
* Все что парсится в классе после константпула - лишь его индексы и размер того, что нужно спарсить.
* Для более подробного анализа вы можете скачать DirtyJOE
Все.java сбилжена.Но как нам что либо редактировать в ней?Лезть в notepad?Давайте это исправим.
Пожалуйста, авторизуйтесь для просмотра ссылки.
пишемmake hotspot-ide-project
и ждем.
Все!
C:\openjdk\build\windows-x86_64-server-release\ide\hotspot-visualstudio
Что ж, половина задачи уже выполнена.
Нам нужен classFileParser.cpp.В нем и происходит основная магия - парсинг класса из байтов.
Заходим в метод parse_stream и видим :
C++:
void ClassFileParser::parse_stream(const ClassFileStream* const stream,
TRAPS) {
assert(stream != NULL, "invariant");
assert(_class_name != NULL, "invariant");
// BEGIN STREAM PARSING
stream->guarantee_more(8, CHECK); // magic, major, minor
// Magic value
const u4 magic = stream->get_u4_fast();
guarantee_property(magic == JAVA_CLASSFILE_MAGIC,
"Incompatible magic value %u in class file %s",
magic, CHECK);
// Version numbers
_minor_version = stream->get_u2_fast();
_major_version = stream->get_u2_fast();
// Check version numbers - we check this even with verifier off
verify_class_version(_major_version, _minor_version, _class_name, CHECK);
stream->guarantee_more(3, CHECK); // length, first cp tag
u2 cp_size = stream->get_u2_fast();
guarantee_property(
cp_size >= 1, "Illegal constant pool size %u in class file %s",
cp_size, CHECK);
_orig_cp_size = cp_size;
if (is_hidden()) { // Add a slot for hidden class name.
cp_size++;
}
_cp = ConstantPool::allocate(_loader_data,
cp_size,
CHECK);
ConstantPool* const cp = _cp;
parse_constant_pool(stream, cp, _orig_cp_size, CHECK);
assert(cp_size == (const u2)cp->length(), "invariant");
// ACCESS FLAGS
stream->guarantee_more(8, CHECK); // flags, this_class, super_class, infs_len
// Access flags
jint flags;
// JVM_ACC_MODULE is defined in JDK-9 and later.
if (_major_version >= JAVA_9_VERSION) {
flags = stream->get_u2_fast() & (JVM_RECOGNIZED_CLASS_MODIFIERS | JVM_ACC_MODULE);
} else {
flags = stream->get_u2_fast() & JVM_RECOGNIZED_CLASS_MODIFIERS;
}
if ((flags & JVM_ACC_INTERFACE) && _major_version < JAVA_6_VERSION) {
// Set abstract bit for old class files for backward compatibility
flags |= JVM_ACC_ABSTRACT;
}
verify_legal_class_modifiers(flags, CHECK);
short bad_constant = class_bad_constant_seen();
if (bad_constant != 0) {
// Do not throw CFE until after the access_flags are checked because if
// ACC_MODULE is set in the access flags, then NCDFE must be thrown, not CFE.
classfile_parse_error("Unknown constant tag %u in class file %s", bad_constant, THREAD);
return;
}
_access_flags.set_flags(flags);
// This class and superclass
_this_class_index = stream->get_u2_fast();
check_property(
valid_cp_range(_this_class_index, cp_size) &&
cp->tag_at(_this_class_index).is_unresolved_klass(),
"Invalid this class index %u in constant pool in class file %s",
_this_class_index, CHECK);
Symbol* const class_name_in_cp = cp->klass_name_at(_this_class_index);
assert(class_name_in_cp != NULL, "class_name can't be null");
// Don't need to check whether this class name is legal or not.
// It has been checked when constant pool is parsed.
// However, make sure it is not an array type.
if (_need_verify) {
guarantee_property(class_name_in_cp->char_at(0) != JVM_SIGNATURE_ARRAY,
"Bad class name in class file %s",
CHECK);
}
#ifdef ASSERT
// Basic sanity checks
if (_is_hidden) {
assert(_class_name != vmSymbols::unknown_class_name(), "hidden classes should have a special name");
}
#endif
// Update the _class_name as needed depending on whether this is a named, un-named, or hidden class.
if (_is_hidden) {
assert(_class_name != NULL, "Unexpected null _class_name");
#ifdef ASSERT
if (_need_verify) {
verify_legal_class_name(_class_name, CHECK);
}
#endif
} else {
// Check if name in class file matches given name
if (_class_name != class_name_in_cp) {
if (_class_name != vmSymbols::unknown_class_name()) {
ResourceMark rm(THREAD);
Exceptions::fthrow(THREAD_AND_LOCATION,
vmSymbols::java_lang_NoClassDefFoundError(),
"%s (wrong name: %s)",
_class_name->as_C_string(),
class_name_in_cp->as_C_string()
);
return;
} else {
// The class name was not known by the caller so we set it from
// the value in the CP.
update_class_name(class_name_in_cp);
}
// else nothing to do: the expected class name matches what is in the CP
}
}
// Verification prevents us from creating names with dots in them, this
// asserts that that's the case.
assert(is_internal_format(_class_name), "external class name format used internally");
if (!is_internal()) {
LogTarget(Debug, class, preorder) lt;
if (lt.is_enabled()){
ResourceMark rm(THREAD);
LogStream ls(lt);
ls.print("%s", _class_name->as_klass_external_name());
if (stream->source() != NULL) {
ls.print(" source: %s", stream->source());
}
ls.cr();
}
}
// SUPERKLASS
_super_class_index = stream->get_u2_fast();
_super_klass = parse_super_class(cp,
_super_class_index,
_need_verify,
CHECK);
// Interfaces
_itfs_len = stream->get_u2_fast();
parse_interfaces(stream,
_itfs_len,
cp,
&_has_nonstatic_concrete_methods,
CHECK);
assert(_local_interfaces != NULL, "invariant");
// Fields (offsets are filled in later)
_fac = new FieldAllocationCount();
parse_fields(stream,
_access_flags.is_interface(),
_fac,
cp,
cp_size,
&_java_fields_count,
CHECK);
assert(_fields != NULL, "invariant");
// Methods
parse_methods(stream,
_access_flags.is_interface(),
&_has_localvariable_table,
&_has_final_method,
&_declares_nonstatic_concrete_methods,
CHECK);
assert(_methods != NULL, "invariant");
if (_declares_nonstatic_concrete_methods) {
_has_nonstatic_concrete_methods = true;
}
// Additional attributes/annotations
_parsed_annotations = new ClassAnnotationCollector();
parse_classfile_attributes(stream, cp, _parsed_annotations, CHECK);
assert(_inner_classes != NULL, "invariant");
// Finalize the Annotations metadata object,
// now that all annotation arrays have been created.
create_combined_annotations(CHECK);
// Make sure this is the end of class file stream
guarantee_property(stream->at_eos(),
"Extra bytes at the end of class file %s",
CHECK);
// all bytes in stream read and parsed
}
u4 - тайпдеф, который означает unsigned 4 byte.Либо же просто unsigned int.
В джаве их 4 вида :
u1, u2, u4, u8 (unsigned byte(C++ char), short, int, long long)
* Довольно смешным фактом является то, что джава сама по себе отрицает значения которые записываются меньше чем в 4 байта.
* Минимальный размер элемента стека - 4 байта.(локали также).wide значения(long,double) вписываются в 2 элемента.
* И в целом byte,boolean,short,char (1-2 byte) реализованы лишь инструкциями взаимодействия с массивами.(ex : SALOAD, SASTORE)(за обработку байтов и булеанок они вообще используют одну инструкцию - BASTORE/BALOAD.Ущемление прав, никак иначе)
stream - обычный класс-ридер потока байтов.имеет на борту методы аля read_u1/2/4/8, и текущий индекс чтения.
Почему все засрано какими то постоянными guarantee_more, assert, check_property?
Если вам тоже интересен этот вопрос как и мне, то ответ хранится в документации openjdk, а именно кодстайлу.
В двух словах, их главным принципом является лучше все проверить, чем поймать рандомный краш вмки.
Для нашей джавы конечно же желательно отказаться от такого дебага(бтв, assert останутся лишь в debug билде).По хорошему их всех удалить, либо покрыть xorstr.
Окей, теперь давайте рассинхронизируем наши задачи, а именно будем параллельно делать парсер, изменять значения, также изменяя их в джаве.
Создадим новый проект в IDE.
Что нам понадобится? Такой же ридер, врайтер.За основу возьмем DataInputStream, DataOutputStream.Он имеет на борту методы read(1 byte), readShort(2 bytes), readInt(4 bytes), readLong(8 bytes), аналогично с write.
В целом такой класс можно и не делать, а юзать DataInputStream, DataOutputStream, но тогда вы теряете возможность добавлять какой либо алгоритм при записи байтов.(ex : xor++ на каждый write изуродует stream при парсинге)
Я сделал для себя 2 класса :
ClassFileStream, ClassFileWriter
Java:
import java.io.*;
public class ClassFileStream {
private final DataInputStream dataInputStream;
public ClassFileStream(byte[] bytes) {
this.dataInputStream = new DataInputStream(new ByteArrayInputStream(bytes));
}
/*
в джава все числовые типы данных signed, unsigned отсутствует.
поэтому лучше обернуть их в int, если понадобится дебаг
*/
public int readByte() throws IOException {
return dataInputStream.read();
}
public int readShort() throws IOException {
return dataInputStream.readShort();
}
public int readInt() throws IOException {
return dataInputStream.readInt();
}
public long readLong() throws IOException {
return dataInputStream.readLong();
}
/*
я не собираюсь переписывать вам полностью ClassFileFormat
этот метод позволит мне дописать остальной стрим в output
*/
public void writeToStream(OutputStream outputStream) throws IOException {
int r;
while ((r = dataInputStream.read()) >= 0) {
outputStream.write(r);
}
}
}
Java:
import java.io.*;
public class ClassFileWriter {
private final ByteArrayOutputStream byteArrayOutputStream;
private final DataOutputStream dataOutputStream;
public ClassFileWriter() {
this.dataOutputStream = new DataOutputStream(byteArrayOutputStream = new ByteArrayOutputStream());
}
/*
в джава все числовые типы данных signed, unsigned отсутствует.
поэтому лучше обернуть их в int, если понадобится дебаг
*/
public void writeByte(int value) throws IOException {
dataOutputStream.write(value);
}
public void writeShort(int value) throws IOException {
dataOutputStream.writeShort(value);
}
public void writeInt(int value) throws IOException {
dataOutputStream.writeInt(value);
}
public void writeLong(long value) throws IOException {
dataOutputStream.writeLong(value);
}
public ByteArrayOutputStream getByteArrayOutputStream() {
return this.byteArrayOutputStream;
}
}
Java:
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.ProtectionDomain;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
public class Main {
private static final byte BYTE_CA = (byte) 0xCA;
private static final byte BYTE_FE = (byte) 0xFE;
private static final byte BYTE_BA = (byte) 0xBA;
private static final byte BYTE_BE = (byte) 0xBE;
public static void main(String[] args) {
if (args.length < 2) {
ProtectionDomain protectionDomain = Main.class.getProtectionDomain();
throw new IllegalArgumentException(String.format("Пример использования java -jar %s in.jar out.jar", protectionDomain.getCodeSource().getLocation().getFile()));
}
try {
String inputJar = args[0];
String outputJar = args[1];
ZipFile inZip = new ZipFile(inputJar);
Enumeration<? extends ZipEntry> zipEntries = inZip.entries();
/*
Создадим мапу path/byte[] для удобного использования ZipFile
*/
Map<String, byte[]> map = new HashMap<>();
while (zipEntries.hasMoreElements()) {
ZipEntry zipEntry = zipEntries.nextElement();
InputStream is = inZip.getInputStream(zipEntry);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int r;
while ((r = is.read()) >= 0) {
byteArrayOutputStream.write(r);
}
map.put(zipEntry.getName(), byteArrayOutputStream.toByteArray());
}
ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(outputJar));
for (Map.Entry<String, byte[]> entry : map.entrySet()) {
byte[] bytes = entry.getValue();
if (bytes.length > 3 && bytes[0] == BYTE_CA && bytes[1] == BYTE_FE && bytes[2] == BYTE_BA && bytes[3] == BYTE_BE) {
ClassFileWriter classFileWriter = new ClassFileWriter();
ClassFile classFile = new ClassFile(new ClassFileStream(bytes), classFileWriter);
classFile.parseStream();
bytes = classFileWriter.getByteArrayOutputStream().toByteArray();
}
zipOutputStream.putNextEntry(new ZipEntry(entry.getKey()));
zipOutputStream.write(bytes);
zipOutputStream.closeEntry();
}
zipOutputStream.close();
inZip.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Создадим класс ClassFile.
Java:
import java.io.IOException;
public class ClassFile {
private ClassFileStream classFileStream;
private ClassFileWriter classFileWriter;
public ClassFile(ClassFileStream classFileStream, ClassFileWriter classFileWriter) {
this.classFileStream = classFileStream;
this.classFileWriter = classFileWriter;
}
public void parseStream() throws IOException {
/*
вот тут расположим наш код
*/
/*
запись всего "not dirty" стрима чо бы потом все воркало
*/
classFileStream.writeToStream(classFileWriter.getByteArrayOutputStream());
}
}
Вспоминаем что я писал выше.
первые четыре байта -> CAFEBABE.Проверим?
Java:
public void parseStream() throws IOException {
int magic = classFileStream.readInt();
System.out.println(String.format("0x%s", Integer.toHexString(magic).toUpperCase()));
/*
запись всего "not dirty" стрима чо бы потом все воркало
*/
classFileStream.writeToStream(classFileWriter.getByteArrayOutputStream());
}
Код:
0xCAFEBABE
Process finished with exit code 0
Теперь перезапишем это, но в другом виде.Это будет маркером в парсере, что класс зашифрован.
Java:
private static final int CUSTOM_MAGIC_VALUE = 0xDEADBEEF;
public void parseStream() throws IOException {
int magic = classFileStream.readInt();
classFileWriter.writeInt(CUSTOM_MAGIC_VALUE);
/*
запись всего "not dirty" стрима чо бы потом все воркало
*/
classFileStream.writeToStream(classFileWriter.getByteArrayOutputStream());
}
Пожалуйста, авторизуйтесь для просмотра ссылки.
Продолжение написания "защиты" во втором туторе.Там мы перепишем parse_constant_pool_entries, соберем и протестим данное дерьмо.
Nursultan Dump :
Пожалуйста, авторизуйтесь для просмотра ссылки.