Минимизировал и прикрепил пример на котором это воспроизводится, библиотека собрана под Linux
Конфиг дефолтный, за исключением включённой системы лицензирования и использованием скрипта vmprotect/script.lua at master · CertainLach/vmprotect · GitHub (TL;DR: на функции начинающиеся с vmprotect_ применяет опции согласно их названию, и удаляет их из экспортов)
Библиотека экспортирует одну функцию - do_the_thing
Функция принимает буфер, первый байт = 4, остальные - лицензионный ключ для системы лицензирования и завершающий \0
При получении лицензионного ключа функция его активирует, делает SetSerialNumber, и если ошибок не было (их логгирует) дёргает функцию защищённую лицензией (lockToKey)
Если передать буфер у которого первый байт = 13, то оно сразу вызывает функцию защищённую лицензией
До применения vmprotect (Запуск с VMProtectSDK заглушками) код успешно работает, если ему передать \x04activationcode\0, то он выведет
Op 4
activate license
set serial
call lock by key function
called
Падений нет
После применения vmprotect при передаче правильного кода лицензии (\x04aaaa-aaaa-aaaa-aaaa\0) - код получает серийник, но падает на вызове функции под защитой
Op 4
activate license
set serial
call lock by key function
terminated by signal SIGSEGV (Address boundary error)
Если же не вызывая активацию попробовать вызвать код под лицензией (Аргумент \x0d), то
Op 13
# И тут появляется окно zenity с ошибкой
Обычные покрытые функции работают правильно (do_the_thing помечена как ultra), падения происходят только на lockByKey.
Этот код удаляет элемент в том числе и из вектора “exports” и вы проскакиваете следующий item после delete:
Похоже что в этом репродюсере проблема действительно с этим
К сожалению в реальном коде это работает несколько иначе, и там функций изначально в .dynsym нет; Значит там другая проблема, переделаю репродюсер.
Однако судя по всему, ElfExports:delete() сейчас не работает, поскольку даже с исправленным скриптом библиотека на выходе падает:
Если экспорты не удалять, то выхлоп работает
Пытался их удалить в нескольких разных хуках, не только в OnBeforeSaveFile, символы из .dynsym успешно исчезают
Вопрос был не про то, что это является причиной креша, а в том, что ваш код LUA работает неправильно и пропускает один из элементов в exports после delete.
В любом случае ваш функционал можно сделать намного проще, т.к. у exports есть itemByAddress:
bprint("And queuing for export removal")
local export = file:exports():itemByAddress(fn:address())
if (export) then
bprint("Deleting export " .. export:name() .. " (" .. address:tostring() .. ")")
export:destroy()
end
deent()
Вопрос был не про то, что это является причиной креша, а в том, что ваш код LUA работает неправильно и пропускает один из элементов в exports после delete.
Это я понял, в последней версии скрипта это исправлено
Однако что исправленный вариант
for i = exports:count(), 1, -1 do
local export = exports:item(i)
local address = export:address();
if tableHas(toDelete, address) then
bprint("Deleting export " .. export:name() .. " (" .. address:tostring() .. ")")
exports:delete(i)
end
end
Что вариант с itemByAddress
for _, address in ipairs(toDelete) do
local export = exports:itemByAddress(address)
if export then
bprint("Deleting export " .. export:name() .. " (" .. address:tostring() .. ")")
export:destroy()
end
end
Приводят к падению библиотеки при попытке её вызова
К сожалению в реальном коде это работает несколько иначе
Оказлось что реальный код падает по той же причине - используется exports:delete, тут по ошибке было подключено 2 версии Rust vmprotect sdk работающие по разным принципам, приватная версия предварительно обрабатывает код через llvm, и там в сошке изначально нет экспортов
Версия с названиями функций (та что публичная, GitHub - CertainLach/vmprotect: Unofficial Rust SDK for VMProtect · GitHub) устроена проще, но в ней для красоты желательно срезать экспорты
Он у вас просто неправильно работал и все.
Я что-то не понимаю что именно с изначальным кодом не так)
Он обходит таблицу exports каждый раз когда нужно удалить экспорт, и как только экспорт найден и удалён - делает break, т.е после удаления не будет проверяться элемент i+1
Я вижу проблему только если бы там было
for i = 1, exports:count() do
local export = exports:item(i)
local address = export:address();
if tableHas(toDelete, address) then
bprint("Deleting export " .. export:name() .. " (" .. address:tostring() .. ")")
exports:delete(i)
end
end
В этом случае действительно после :delete() произойдёт сдвиг элементов, и элемент i+1 будет пропущен
Спасибо, на репродюсере помогло
Но к сожалению на реальном коде оно всё ещё падает
Приблизил репродюсер к реальному, на нём падает; апи библиотеки аналогичное, послал в ПМ
У вас do_the_thing работает через символ “vmprotect_ultra_do_the_thing_inner_186237384678405675810701436432396487933_ptr”, который вы удаляете вместе с экспортом:
.text:000000000007DDC0 public do_the_thing
.text:000000000007DDC0 do_the_thing proc near ; DATA XREF: LOAD:00000000000007F8↑o
.text:000000000007DDC0 ; __unwind {
.text:000000000007DDC0 jmp cs:vmprotect_ultra_do_the_thing_inner_186237384678405675810701436432396487933_ptr
.text:000000000007DDC0 ; } // starts at 7DDC0
.text:000000000007DDC0 do_the_thing endp
Надо выкинуть из библиотеки использование экспортов, и по нормальному названию матчить, проблема лишь в том что оно mangled, и vmprotect не может названия обработать
Сделал так, + через FFI подключил demangler, и решил кучу проблем
Было бы однако неплохо если бы сам vmprotect научился это делать. Я так понимаю используемая сейчас библиотека притворяется что умеет demangling Rust символов, однако зачастую у неё ничего не выходит (везде mangled названия)
В Rust дефолтная (legacy) схема mangling не стабильная, а v0 схему нужно включать явно, и её никто не поддерживает
Единственный правильный demangler - GitHub - rust-lang/rustc-demangle: Rust symbol demangling · GitHub
local rustc_demangle_lib = vmprotect.openLib("/path/to/librustc_demangle.so")
if not rustc_demangle_lib then
error("failed to open librustc_demangle")
end
local rustc_demangle = rustc_demangle_lib:getFunction("rustc_demangle", "int", "string", "pointer", "size_t")
local c_lib = vmprotect.openLib("/path/to/libc.so.6")
if not c_lib then
error("failed to open libc")
end
local malloc = c_lib:getFunction("malloc", "pointer", "size_t")
local free = c_lib:getFunction("malloc", "void", "pointer")
local strdup = c_lib:getFunction("strdup", "string", "pointer")
function demangle(identifier)
local demangle_buf = malloc(4096)
local res_code = rustc_demangle(identifier, demangle_buf, 4096)
local res = nil
if res_code == 0 then
-- Buffer is too small, or the string is not a valid rust identifier
goto free
end
-- I think memory leak occurs here?.. Haven't found a good way to convert pointer to string in vmprotect lua api, any identity function would work
res = strdup(demangle_buf)
::free::
free(demangle_buf)
return res
end
if demangle("_RNvCskwGfYPst2Cb_3foo16example_function") ~= "foo::example_function" then
error("demangler is not working properly")
end
Было бы однако неплохо если бы сам vmprotect научился это делать. Я так понимаю используемая сейчас библиотека притворяется что умеет demangling Rust символов, однако зачастую у неё ничего не выходит (везде mangled названия)
У вас сейчас там вообще нет никаких символов кроме экспорта.
У вас сейчас там вообще нет никаких символов кроме экспорта.
В данном случае потому что библиотека стрипнута
Сейчас flow в vmprotect rust sdk - функция с маркером
#[protect(ultra)]
fn add(a: u32, b: u32) -> u32 {
a + b
}
Преобразуется в
#[inline(always)]
fn add(a: u32, b: u32) -> u32 {
#[inline(never)]
#[no_mangle]
fn vmprotect_ultra_add_0123456789(a: u32, b: u32) {
a + b
}
vmprotect_ultra_add_0123456789(a, b)
}
#[no_mangle]
в Rust означает одновременно и то что название функции не надо mangleить, и то что её она имеет external linkage (К сожалению, без экспорта оно не умеет): Precise semantics of `no_mangle`? - compiler - Rust Internals, отсюда и выходят экспорты вида vmprotect_ultra_add_0123456789
Я собрал бинарь без strip, и убрал
#[no_mangle]
(отправил в PM), однако в таком виде скрипт разумеется не работает, потому что у функций уже не те названия которые он ожидает
С таким кодом нужно дополнительно парсить названия
local name, protinfo = name:match("^(.+)::vmprotect_(.+)::h" .. string.rep("[0-9]", 16) .. "$")
Однако с таким кодом парсинга тут пропускаются методы вроде vmprotect_ultra_biguints_from_str_4, потому что vmprotect для них name выдаёт в mangled виде
Чтобы не приходилось возиться с экспортами, vmprotect по хорошему должен уметь делать demangling
Определить что для символа стоит использовать librustc_demangle вместо того что сейчас vmprotect использует сейчас можно по одному из двух признаков:
Префикс: _Z (по аналогии с C++, однако там возможный синтаксис шире чем встречается в C++)
Суффикс: 17hXXXXXXXXXXXXXXXXE (X-hexadecimal number, ::h… суффикс в Rust означает какому пакету принадлежит функция)
После которого опционально следует .llvm.XXX… (X - uppercase hex / _ / @)
legacy схема mangling rust, библиотека что я прислал собрана с ней
Префикс: _R
Суффикс .llvm.XXX… тут тоже может присутствовать (Он добавляется когда включена параллельная компиляция кода)
На самом деле немного не так. Он пересекается с GNU_V3, поэтому деманглинг от GCC частично обрабатывает символы от Rust:
Это я понимаю, под признаками я имел в виду как отличить идентификаторы что gnu v3 demangler будет неправильно переваривать - у Rust всегда последний компонент это ::h0000000000000000, но вы и сами разобрались судя по всему
Красота какая, теперь в Rust при желании можно не декларативно через код выбирать функции для защиты, спасибо!