Вынес этот гайд из своего сообщения.
Мы будем разбирать этот crackme скрипт
После снятия протекторов и обфускации получаем такой код:
Разбирать как декомпилировать скрипты мы будем не в этом мини-гайде. Тут нас интересуют только способы защиты.
Запускать код мы будем через LuaJIT, чтобы пройти все проверки на проксирование (которых тут 7).
Если запускать через moonloader (будет в конце гайда), то мы сразу попадём в стадию отправки запроса, что не интересно.
Ниже будут куски кода, для обхода конкретных проверок. С каждой проверкой код может меняться. В приложении будет конечный файл obf.lua
При запуске сразу натыкаемся на ошибку, связанную с moonloader API, а именно отсутствование функций
Поставим заглушки на moonloader API:
Теперь ошибки нет, но срабатывает проверка
Давайте и её обманем:
Как это работает:
В lua есть 2 типа функций: "C" и "Lua".
Идем дальше, ошибка:
Принцип тот же, только уже функции проверяют сами себя.
Вот мы прошли все проверки на проксирование. Вызываем
Натыкаемся на бесконечный цикл из-за
Изменим таблицу moonAPI:
Теперь будет бесконечно ожидать функцию
Тут мы пошли путем подмены ответа.
Т.к оригинальный сайт не работает, мы не можем узнать какой там был ответ, поэтому делаем его сами по шаблону кода.
В секции code динамичный ключ, который зависит от os.time и math.random.
Перед запуском main мы можем подменить эти функции:
Lua никогда не сможет быть не обманутым.
Как я уже говорил, всё это мы делали для обычного LuaJIT, поэтому пришлось ставить заглушки на многие функции.
Ниже прикреплю код файлом obf2.lua, который нужен для склейки с test-obf-x2-prot.luac и использовании уже в игре.
Для склейки я буду использовать свой веб инжектор, но можно также использовать любой склейщик (смотри "маленькую тонкость" ниже)
obf2.lua нужно скомпилировать и выбрать в такой последовательности
На выходе получаем файл obf2-inject.luac, закидываем в игру и вот скрин из игры:
(написал команду /crackme в чат)
Маленькая тонкость в obf2.lua:
При использовании инжектора можно убрать обманку
В
Ведь мы именно *внедряемся/инжектимся* в файл, а не *склеиваемся*. Из-за этого уровень стека не изменяется.
Мы будем разбирать этот crackme скрипт
После снятия протекторов и обфускации получаем такой код:
Lua:
local slot0_a1000 = string
local slot1_a1001 = slot0_a1000.char
local slot2_a1002 = slot0_a1000.byte
local slot3_a1003 = slot0_a1000.format
local slot4_a1004 = slot0_a1000.gsub
local slot5_a2659 = _G
local function slot6_a2654(arg0_a1016)
local slot1_a1022 = ""
for slot5_a1017 = 2, arg0_a1016[1] do
slot1_a1022 = slot1_a1022 .. slot1_a1001(arg0_a1016[slot5_a1017] - slot5_a1017)
end
return slot1_a1022
end
local function slot7_a1026(arg0_a1024)
return arg0_a1024 + 483948592
end
local function slot8_a1052(arg0_a1040)
local slot1_a1036 = slot6_a2654({
1
})
for slot5_a1041 = 2, #arg0_a1040 do
slot1_a1036 = slot1_a1036 .. slot1_a1001(arg0_a1040[slot5_a1041] + arg0_a1040[slot7_a1026(-483948591)])
end
return slot1_a1036
end
local slot9_a1059 = require("ffi")
local function slot10_a2652(arg0_a1127)
if #arg0_a1127 == 0 then
return slot6_a2654({
1
})
end
arg0_a1127 = slot4_a1004(slot4_a1004(slot4_a1004(arg0_a1127, "£‘", "õ"), "£‹", "ö"), "££", "£")
local slot1_a1125 = slot9_a1059["new"](slot3_a1003("uint8_t[%d]", #arg0_a1127 + 1), arg0_a1127)
for slot5_a1117 = 0, #arg0_a1127 do
slot1_a1125[slot5_a1117] = 255 - slot1_a1125[slot5_a1117]
end
return slot9_a1059["string"](slot1_a1125, #arg0_a1127)
end
local function slot11_a1158(arg0_a1130, arg1_a1131, ...)
return sampAddChatMessage(string["format"](arg1_a1131, ...), arg0_a1130)
end
local function slot12_a1164(arg0_a1159, ...)
return slot11_a1158(-1, arg0_a1159, ...)
end
local function slot13_a1225()
local slot0_a1180 = require("ffi")
slot0_a1180["cdef"]([[int __stdcall GetVolumeInformationA(
const char* lpRootPathName,
char* lpVolumeNameBuffer,
uint32_t nVolumeNameSize,
uint32_t* lpVolumeSerialNumber,
uint32_t* lpMaximumComponentLength,
uint32_t* lpFileSystemFlags,
char* lpFileSystemNameBuffer,
uint32_t nFileSystemNameSize
);
]])
local slot1_a1203 = slot0_a1180["new"]("unsigned long[1]", 0)
slot0_a1180["C"]["GetVolumeInformationA"](nil, nil, 0, slot1_a1203, nil, nil, nil, 0)
return slot1_a1203[0]
end
local slot14_a2650 = 0
local slot15_a1231 = "\\"
main = function()
if 2 == 1 then
return 1
elseif 1 > 2 then
return 0
elseif 1 > 2 then
return true
elseif 1 ~= 1 then
return false
elseif 1 == 2 then
return nil
elseif 2 > 2 then
return
end
repeat
doNothing()
until isSampAvailable()
wait(500)
slot12_a1164("[CrackMe] Script Loaded")
wait(500)
slot12_a1164("[CrackMe] Connecting to the server...")
math["randomseed"](os["clock"]() * 100000000000)
local slot0_a1709 = math["random"](1234, 7890) .. math["random"](123123, 999999)
local slot1_a1750 = os["time"]()
local slot3_a1401 = "%3A"
local slot4_a1449 = slot1_a1750 * string["sub"](slot0_a1709, 1, 4) +
string["sub"](slot0_a1709, 5, #slot0_a1709)
local slot2_a1450 = slot0_a1709 .. slot3_a1401 .. slot4_a1449
local slot3_a1455 = "https://luajit.ru/crackme?k=2d46423d4565443e58684b792d252e64&s="
local slot4_a1458 = slot13_a1225()
local slot5_a1463 = "&h="
local slot3_a1465 = slot3_a1455 .. slot4_a1458 .. slot5_a1463 .. slot2_a1450
local slot4_a1477 = os.getenv("TEMP") .. slot15_a1231 .. slot1_a1750
local slot5_a1679 = nil
local slot6_a1760 = nil
local slot7_a1722 = nil
downloadUrlToFile(slot3_a1465, slot4_a1477, function(arg0_a1490, arg1_a1491)
if arg1_a1491 == 6 then
local slot2_a1529 = io["open"](slot4_a1477, "r")
if slot2_a1529 then
io["input"](slot2_a1529)
slot5_a1679 = io["read"]("*a")
io["close"](slot2_a1529)
os["remove"](slot4_a1477)
if string["find"](slot5_a1679, "::ok::") then
slot12_a1164("[CrackMe] Connected.")
slot7_a1722 = string["match"](slot5_a1679, "::code::(%d+)::")
slot6_a1760 = string["match"](slot5_a1679, "::f::(%w+)::")
else
slot12_a1164("[CrackMe] Something went wrong. Contact the script developer.")
end
else
slot12_a1164("[CrackMe] Connection error.")
end
end
end)
repeat
doNothing()
until slot5_a1679 and slot14_a2650 < 80
local slot8_a1693 = string["match"](slot5_a1679, "::message::(.*)::")
if slot8_a1693 then
return slot12_a1164("[CrackMe] " .. slot8_a1693)
end
if (slot7_a1722 - string["sub"](slot0_a1709, 1, 4)) / string["sub"](slot0_a1709, 5, 8) ~= slot1_a1750 or slot14_a2650 >= 80 then
return slot12_a1164("[CrackMe] Something went wrong")
end
slot5_a2659[slot6_a1760]()
slot5_a2659[slot6_a1760] = nil
wait("-1")
end
local function slot16_a2536(arg0_a1797)
if type(arg0_a1797) == "number" then
return arg0_a1797 - 971724
end
return arg0_a1797
end
doNothing = function()
return wait(0)
end
collectgarbage = function()
local slot0_a1827 = "2128518625"
sampAddChatMessage("[CrackMe] You are signed in. Use /crackme", -1)
sampRegisterChatCommand("crackme", function()
sampAddChatMessage("[CrackMe] it works!", -1)
end)
end
local slot18_a1932 = "string"
local slot19_a1933 = {
"sub",
"match",
"find",
"dump"
}
local slot18_a1958 = "math"
local slot19_a1959 = {
"random",
"randomseed"
}
local slot18_a1974 = "os"
local slot19_a1975 = {
"time",
"clock",
"remove"
}
local slot18_a1995 = "io"
local slot19_a1996 = {
"open",
"close",
"write",
"read",
"input",
"output"
}
local slot18_a2031 = "debug"
local slot19_a2032 = {
"getinfo"
}
local slot17_a2145 = {
"downloadUrlToFile",
"wait",
"sampAddChatMessage",
"sampRegisterChatCommand",
"isSampAvailable",
"type",
"tostring",
"tonumber",
"require",
"pcall",
"pairs",
[slot18_a1932] = slot19_a1933,
[slot18_a1958] = slot19_a1959,
[slot18_a1974] = slot19_a1975,
[slot18_a1995] = slot19_a1996,
[slot18_a2031] = slot19_a2032
}
for slot21_a2059, slot22_a2106 in pairs(slot17_a2145) do
if type(slot21_a2059) ~= "string" and (pcall(string["dump"], slot5_a2659[slot22_a2106]) or debug["getinfo"](slot5_a2659[slot22_a2106])["what"] ~= "C") then
local slot14_a2121 = slot14_a2650 + 100
print("[CrackMe] err #0")
while true do
end
end
end
for slot21_a2226, slot22_a2178 in pairs(slot17_a2145) do
if type(slot21_a2226) ~= "number" then
for slot26_a2183, slot27_a2228 in pairs(slot22_a2178) do
if pcall(string["dump"], slot5_a2659[slot21_a2226][slot27_a2228]) or debug["getinfo"](slot5_a2659[slot21_a2226][slot27_a2228])["what"] ~= "C" then
local slot14_a2243 = slot14_a2650 + 100
print("[CrackMe] err #1")
while true do
end
end
end
end
end
local slot18_a2318 = "oBWxaZnptb13cd6znDi4r6rWc6hq7hhfmjfNyfAjCdF9LjZA3pqLEyPZAZVdVosGygk3xA6KCdgSq" ..
string["dump"](slot16_a2536)
if string["sub"](slot18_a2318, 84, 84) ~= "@" or string["sub"](slot18_a2318, 79, 80) ~= "LJ" then
local slot14_a2329 = slot14_a2650 + 100
print("[CrackMe] err #2")
while true do
end
end
if debug["getinfo"](2) then
local slot14_a2362 = slot14_a2650 + 100
print("[CrackMe] err #3")
while true do
end
end
local slot19_a2389, slot20_a2404 = pcall(slot16_a2536, 971801)
if type(pcall) ~= "function" or slot20_a2404 ~= 77 or not pcall(slot16_a2536) or not pcall then
local slot14_a2419 = slot14_a2650 + 100
print("[CrackMe] err #4")
while true do
end
end
if not debug["getinfo"] or not debug["getinfo"](1) or type(debug["getinfo"]) ~= "function" or type(debug["getinfo"](1)) ~= "table" or debug["getinfo"](slot16_a2536)["what"] ~= "Lua" then
local slot14_a2551 = slot14_a2650 + 100
print("[CrackMe] err #5")
while true do
end
end
if type(false) ~= "boolean" or type(0) ~= "number" or type("") ~= "string" or type({}) ~= "table" or type(function()
end) ~= "function" then
local slot14_a2651 = slot14_a2650 + 100
print("[CrackMe] err #6")
while true do
end
end
Разбирать как декомпилировать скрипты мы будем не в этом мини-гайде. Тут нас интересуют только способы защиты.
Запускать код мы будем через LuaJIT, чтобы пройти все проверки на проксирование (которых тут 7).
Если запускать через moonloader (будет в конце гайда), то мы сразу попадём в стадию отправки запроса, что не интересно.
Ниже будут куски кода, для обхода конкретных проверок. С каждой проверкой код может меняться. В приложении будет конечный файл obf.lua
При запуске сразу натыкаемся на ошибку, связанную с moonloader API, а именно отсутствование функций
Поставим заглушки на moonloader API:
Lua:
local moonAPI = {
"downloadUrlToFile",
"wait",
"sampAddChatMessage",
"sampRegisterChatCommand",
"isSampAvailable",
}
for index, value in ipairs(moonAPI) do
_G[value] = function()
end
end
Теперь ошибки нет, но срабатывает проверка
print("[CrackMe] err #0")
Давайте и её обманем:
Lua:
local stringDump = string.dump
string.dump = function()
for index, value in ipairs(moonAPI) do
if _G[value] == func then
return error()
end
end
return stringDump(func)
end
local debugGetinfo = debug.getinfo
debug.getinfo = function(f, what)
for index, value in ipairs(moonAPI) do
if _G[value] == f then
return {
what = "C"
}
end
end
return debugGetinfo(f, what)
end
Как это работает:
В lua есть 2 типа функций: "C" и "Lua".
- "С" тип получают все стандартные функции (
tostring
,print
и тд) + все функции moonloader (wait
,downloadUrlToFile
и тд). - "Lua" тип получают все объявленные функции в самом коде lua
string.dump
возвращает исключение при попытке дампа "C" функции.downloadUrlToFile
это и есть "C" функция, но мы её перезаписываем и получаем уже "Lua" функцию. Поэтому мы и обманываем string.dump
.debug.getinfo
позволяет узнать тип функции, "C" или "Lua". Как мы поняли выше, нам нужно обманом вернуть тип "C".Идем дальше, ошибка:
print("[CrackMe] err #1")
Lua:
local stringDump = string.dump
string.dump = function(func)
if func == string.dump or func == debug.getinfo then
return error()
end
return stringDump(func)
end
local debugGetinfo = debug.getinfo
debug.getinfo = function(f, what)
if f == string.dump or f == debug.getinfo then
return {
what = "C"
}
end
return debugGetinfo(f, what)
end
print("[CrackMe] err #2")
не срабатывает. т.к string.sub
у нас оригинальнаяprint("[CrackMe] err #3")
срабатывает
Lua:
debug.getinfo = function(f, what)
if f == 2 then
return nil
end
return debugGetinfo(f, what)
end
print("[CrackMe] err #4")
не срабатывает. т.к pcall
у нас оригинальныйprint("[CrackMe] err #5")
не срабатывает. т.к debug.getinfo
отрабатывает в штатном режимеprint("[CrackMe] err #6")
не срабатывает. т.к type
у нас оригинальныйВот мы прошли все проверки на проксирование. Вызываем
main()
Натыкаемся на бесконечный цикл из-за
isSampAvailable
Изменим таблицу moonAPI:
Lua:
local moonAPI = {
downloadUrlToFile = function ()
end,
wait = function ()
end,
sampAddChatMessage = print,
sampRegisterChatCommand = function ()
end,
isSampAvailable = function ()
return true
end,
}
for index, value in pairs(moonAPI) do
_G[index] = value
end
Теперь будет бесконечно ожидать функцию
downloadUrlToFile
. Её тоже дополним
Lua:
downloadUrlToFile = function(url, path, callback)
local file = io.open(path, 'w')
assert(file, 'path invalid')
file:write([[
::ok::
::code::0::
::f::collectgarbage::
]])
file:close()
callback(nil, 6 --[[END DOWNLOAD]])
end
Т.к оригинальный сайт не работает, мы не можем узнать какой там был ответ, поэтому делаем его сами по шаблону кода.
В секции code динамичный ключ, который зависит от os.time и math.random.
Перед запуском main мы можем подменить эти функции:
Lua:
math.random = function()
return "110"
end
os.time = function()
return -110.1
end
- Почему проверка на проксирование не срабатывает?
Потому что проверка срабатывает 1 раз при запуске скрипта. После успешного запуска мы можем делать что хотим. - Что за числа в os.time и math.random?
При таких числах проверка:
(slot7_a1722 - string["sub"](slot0_a1709, 1, 4)) / string["sub"](slot0_a1709, 5, 8) ~= slot1_a1750 or slot14_a2650 >= 80
... будет всегда возвращать истину.
slot7_a1722
это секция ::code::, у нас она ровна нулю
В секции :f:: написано название функции, это collectgarbage.
Итог:
Таким способом можно обманывать любой lua(c) скрипт, обходить ЛЮБЫЕ проверки.Lua никогда не сможет быть не обманутым.
Как я уже говорил, всё это мы делали для обычного LuaJIT, поэтому пришлось ставить заглушки на многие функции.
Ниже прикреплю код файлом obf2.lua, который нужен для склейки с test-obf-x2-prot.luac и использовании уже в игре.
Для склейки я буду использовать свой веб инжектор, но можно также использовать любой склейщик (смотри "маленькую тонкость" ниже)
obf2.lua нужно скомпилировать и выбрать в такой последовательности
На выходе получаем файл obf2-inject.luac, закидываем в игру и вот скрин из игры:
(написал команду /crackme в чат)
Маленькая тонкость в obf2.lua:
При использовании инжектора можно убрать обманку
В
debug.getinfo
Lua:
if f == 2 then
return nil
end