Начинающий
- Статус
- Оффлайн
- Регистрация
- 25 Мар 2021
- Сообщения
- 11
- Реакции
- 2
Привет! Я еще давно занимался изучением Nuitka, и лишь совсем недавно задумался о написании статьи, что верно ведь тогда, год назад, у меня не было должного представления о нуитке и получилось бы, что я выложил чушь из своих догадок.
Что такое Nuitka?
Обычно Нуитку используют вместо PyInstaller из-за его небезопасности, ведь в Nuitka нельзя обойтись без реверса и должной базы знаний. Не плюйтесь в экран, но в большинстве известных мне случаев, Нуитку используют для накрытия ратников, или тулок для маленьких доксеров.
Я думаю что есть люди заинтересованные в том чтобы крякнуть какую-нибудь прогу с нуиткой или хотя-бы научиться. Для них я и пишу статью.
В этой статье мы будем разбирать как тестовую программку, так и реальный уникализатор с лолза.
Начинаем!
Устанавливаем Nuitka:
Пишем программку:
Компилируем тестовую программку:
nuitka --debug --trace-execution [nuitka_debug.py]
Нас интересуют только билд и pdb файл. Создаем папку и кидаем всё туда.
Запускаем IDA, открываем билд и загружаем pdb.
Нас интересуют две функции: loadConstantsBlob и modulecode__main__
Когда вы будете реверсить у вас не будет названий функций слева сбоку, поэтому я даю вам их отличительные признаки.
Чтобы найти loadConstantsBlob переходим к вызову FindResourceA, ведь эту функцию вызывает только loadConstantsBlob (насколько мне известно - всегда).
Чтобы найти modulecode__main__ переходим к PyMarshal_ReadObjectFromString, рядом находиться "call REG" который и ведет к мэйну ИЛИ
находим строку "__main__" и смотрим функцию, которая вызывает loadConstantsBlob с __main__ как аргумент
На пикче конечно ничего не ясно, но Nuitka таким образом пакует ресурсы. Взято при компиляции тестовой программки.
Зато видны строки, пароль, переменные, аргументы и значения аргументов.
Если интересно как это распаковывается, то рассмотрите
modulecode__main__ занимается второй половиной - выполнением кода на основе констант.
Начинается main с использования констант для создания объектов кода. На примере программки это функции main, exit и <module>
mod_consts это список констант
Стоит учесть что почти все переменные здесь - объекты структуры PyObject*, которая является базой для всех типов в CPython
Если вы хотите прочитать данные переменной то нужно сначала узнать к какому типу она относится как к объекту (ob_type)
В большинстве случаев можно прочитать PyObject* по оффсету 0x30
Далее создаётся исполняемый фрейм
В основном он будет использоваться для хранения внутри номера текущей строки, что поможет при отладке
После импорта пары модулей собираются функции на основе ранее полученных объектов кода
И вот, после полной инициализации программа приступает к выполнению кода.
_result = CALL_FUNCTION_NO_ARGS(tstate, main_func); запускает функцию main
Переходим к main (impl___main_____function__2_main)
Анализируем... понимаем, что фрейм создается для каждой функции, следовательно и следить за строками будет проще
Уделяем внимание тому, что для вызова функций используется CALL_FUNCTION с различными вариациями, чтоможет вызвать сложности 100% вызовет сложности при анализе ил восстановлении кода без pdb.
Перейдем к части с проверкой пароля
Все вроде интуитивно понятно, поэтому не буду заморачиваться
После длительной проверки данных ассертами наконец переходим к сравнению строк.
Очень заметна функция COMPARE_EQ_CBOOL_UNICODE_UNICODE (return_value, operand1, operand2) которая и занимается сравнением == в питоне.
В моем случае я просто поменял jz на nop и крякми решен!
Отлично, почти и не напрягались, а уже ломанули.
Во второй части мы попробуем ломануть уникализатор видео и фото - AVAMA
Кидаем аваму в DIE
Упаковщик: Nuitka[OneFile] говорит нам о том, что авама была скомпилирована с флагом --onefile
На гитхабе давно валяется
Кидаем экстрактор в одну папку с билдом и прописываем в командной строке
Далее будем работать с экстрактом в папке extracted. В новых версиях при --onefile в эксракте будет не .exe а .dll поэтому вам придется написать лоадер для запуска.
Кидаем все файлы из оригинальной папки в _extracted ведь без этого софт не запустится.
Важный момент: При KeyboardInterrupt Нуитка стучит в консоль номер последней исполняемой строки т.е. пока программа ожидает ввод пользователя мы можем нажать ctrl+c и узнать где находится ввод.
Например:
Запускаем IDA и загружаем софт, сразу переходим к мэйну. Находим "__main__" и переходим на modulecode__main__. Так как кодовая база у тестового билда и крякми почти одинаковая, то можно расставить названия важных функций по местам.
Так как мы собираемся отталкиваться от номера строки, то нам нужно найти переменную содержащую номер, у меня их две - MAIN_FRAME и traceback_lineno
Теперь находим нашу строку - 1248
Я посмотрел, поставил бряки и нашел точку сравнения ключей - 1245 строка
PySequence_Conatins() выполняет команду "in" в питоне - поиск данных в списке.
В нашем случае это можно изобразить как:
Патчим...
Оууу дяяяя
Большое спасибо за прочтение, это была моя первая статья.
Что такое Nuitka?
Nuitka - это "компилятор" для питона написанный на си, который не обходится без DLL питона и костылей. Думаю так будет объективней." Nuitka is a Python compiler written in Python."


Я думаю что есть люди заинтересованные в том чтобы крякнуть какую-нибудь прогу с нуиткой или хотя-бы научиться. Для них я и пишу статью.
В этой статье мы будем разбирать как тестовую программку, так и реальный уникализатор с лолза.
Начинаем!
Устанавливаем Nuitka:
pip install nuitka
Пишем программку:
в предпросмотре тема питона тошнотная

nuitka_debug.py:
### SOURCE CODE ###
import os
def exit(a,b,c):
return os._exit(a+b+c)
def main():
print("Hello, YOUGAME!")
password = input("Enter a password: ")
if password == "gaga": print("GREAT")
else: print("WRONG")
os.system("pause") #Ждем перед выходом из программы
a,b,c = 1,2,3
exit(a,b,c) # Смотрим на то, как будут вести себя простейшие операции
if __name__ == "__main__": main()
Компилируем тестовую программку:
В процессе компиляции использовалась Nuitka 2.7.4
Нас интересуют только билд и pdb файл. Создаем папку и кидаем всё туда.
Запускаем IDA, открываем билд и загружаем pdb.
Нас интересуют две функции: loadConstantsBlob и modulecode__main__
Когда вы будете реверсить у вас не будет названий функций слева сбоку, поэтому я даю вам их отличительные признаки.
Чтобы найти loadConstantsBlob переходим к вызову FindResourceA, ведь эту функцию вызывает только loadConstantsBlob (насколько мне известно - всегда).
Чтобы найти modulecode__main__ переходим к PyMarshal_ReadObjectFromString, рядом находиться "call REG" который и ведет к мэйну ИЛИ
находим строку "__main__" и смотрим функцию, которая вызывает loadConstantsBlob с __main__ как аргумент
Что же делают эти функции?
loadConstantsBlob распаковывает ресурсы программы при том, что имеет структуру VM. Nuitka, подготавливаясь к компиляции, упаковывает ресурсы скрипта в файл __constants.bin. В билде ресурсы находятся в RCDATA. Ресурсы скрипта - это названия переменных, аргументы функций, типы данных, значения переменных, названия импортируемых модулей и прочее. В дальнейшем распакованные ресурсы будут упоминаться как константы
На пикче конечно ничего не ясно, но Nuitka таким образом пакует ресурсы. Взято при компиляции тестовой программки.
Зато видны строки, пароль, переменные, аргументы и значения аргументов.
Если интересно как это распаковывается, то рассмотрите
Пожалуйста, авторизуйтесь для просмотра ссылки.
modulecode__main__ занимается второй половиной - выполнением кода на основе констант.
Начинается main с использования констант для создания объектов кода. На примере программки это функции main, exit и <module>

mod_consts это список констант
Стоит учесть что почти все переменные здесь - объекты структуры PyObject*, которая является базой для всех типов в CPython
Если вы хотите прочитать данные переменной то нужно сначала узнать к какому типу она относится как к объекту (ob_type)
В большинстве случаев можно прочитать PyObject* по оффсету 0x30
Далее создаётся исполняемый фрейм
CURRENT_FRAME = MAKE_COMPILED_FRAME(module_code_object, _module, ob_refcnt, 0);
В основном он будет использоваться для хранения внутри номера текущей строки, что поможет при отладке
После импорта пары модулей собираются функции на основе ранее полученных объектов кода

И вот, после полной инициализации программа приступает к выполнению кода.
_result = CALL_FUNCTION_NO_ARGS(tstate, main_func); запускает функцию main
Переходим к main (impl___main_____function__2_main)
Анализируем... понимаем, что фрейм создается для каждой функции, следовательно и следить за строками будет проще

Уделяем внимание тому, что для вызова функций используется CALL_FUNCTION с различными вариациями, что
Перейдем к части с проверкой пароля

Все вроде интуитивно понятно, поэтому не буду заморачиваться
После длительной проверки данных ассертами наконец переходим к сравнению строк.


Очень заметна функция COMPARE_EQ_CBOOL_UNICODE_UNICODE (return_value, operand1, operand2) которая и занимается сравнением == в питоне.
В моем случае я просто поменял jz на nop и крякми решен!

Отлично, почти и не напрягались, а уже ломанули.
Вторая часть
Во второй части мы попробуем ломануть уникализатор видео и фото - AVAMA
Кидаем аваму в DIE

Упаковщик: Nuitka[OneFile] говорит нам о том, что авама была скомпилирована с флагом --onefile
На гитхабе давно валяется
Пожалуйста, авторизуйтесь для просмотра ссылки.
, который нам поможет распаковать билд.Кидаем экстрактор в одну папку с билдом и прописываем в командной строке
nuitka-extractor [onefile_build].exe
Далее будем работать с экстрактом в папке extracted. В новых версиях при --onefile в эксракте будет не .exe а .dll поэтому вам придется написать лоадер для запуска.
Кидаем все файлы из оригинальной папки в _extracted ведь без этого софт не запустится.

Важный момент: При KeyboardInterrupt Нуитка стучит в консоль номер последней исполняемой строки т.е. пока программа ожидает ввод пользователя мы можем нажать ctrl+c и узнать где находится ввод.
Например:

Запускаем IDA и загружаем софт, сразу переходим к мэйну. Находим "__main__" и переходим на modulecode__main__. Так как кодовая база у тестового билда и крякми почти одинаковая, то можно расставить названия важных функций по местам.
Так как мы собираемся отталкиваться от номера строки, то нам нужно найти переменную содержащую номер, у меня их две - MAIN_FRAME и traceback_lineno

Теперь находим нашу строку - 1248

Я посмотрел, поставил бряки и нашел точку сравнения ключей - 1245 строка

PySequence_Conatins() выполняет команду "in" в питоне - поиск данных в списке.
В нашем случае это можно изобразить как:
compare_keys:
if key not in keys:
print("fuck")
exit()
Патчим...
было:
mov rdx,rbx
mov rcx,rax
call qword ptr ds:[<PySequence_Contains>]
cmp eax,FFFFFFFF
jne avama_unik.7FF6E8652CA3
mov rax,qword ptr ds:[rdi+60]
mov r15d,4DD
mov qword ptr ss:[rsp+68],rax
mov rax,qword ptr ds:[rdi+68]
mov qword ptr ss:[rsp+70],rax
mov rax,qword ptr ds:[rdi+70]
mov qword ptr ss:[rsp+60],rax
xor eax,eax
mov qword ptr ds:[rdi+60],rax
mov qword ptr ds:[rdi+68],rax
mov qword ptr ds:[rdi+70],rax
jmp avama_unik.7FF6E86549AD
test eax,eax ###
sete cl ###
стало:
mov rdx,rbx
mov rcx,rax
call qword ptr ds:[<PySequence_Contains>]
cmp eax,FFFFFFFF
jne avama_unik.7FF6E8652CA3
mov rax,qword ptr ds:[rdi+60]
mov r15d,4DD
mov qword ptr ss:[rsp+68],rax
mov rax,qword ptr ds:[rdi+68]
mov qword ptr ss:[rsp+70],rax
mov rax,qword ptr ds:[rdi+70]
mov qword ptr ss:[rsp+60],rax
xor eax,eax
mov qword ptr ds:[rdi+60],rax
mov qword ptr ds:[rdi+68],rax
mov qword ptr ds:[rdi+70],rax
jmp avama_unik.7FF6E86549AD
mov cl,0
nop
nop
nop

Оууу дяяяя
Большое спасибо за прочтение, это была моя первая статья.