Исходник Гайд Crack/Проксирование скриптов

Вынес этот гайд из своего сообщения.

Мы будем разбирать этот 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".
  1. "С" тип получают все стандартные функции (tostring, print и тд) + все функции moonloader (wait, downloadUrlToFile и тд).
  2. "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.
В итоге скрипт успешно крякнут:

1680436943629.png

Итог:

Таким способом можно обманывать любой lua(c) скрипт, обходить ЛЮБЫЕ проверки.
Lua никогда не сможет быть не обманутым.


Как я уже говорил, всё это мы делали для обычного LuaJIT, поэтому пришлось ставить заглушки на многие функции.
Ниже прикреплю код файлом obf2.lua, который нужен для склейки с test-obf-x2-prot.luac и использовании уже в игре.
Для склейки я буду использовать свой веб инжектор, но можно также использовать любой склейщик (смотри "маленькую тонкость" ниже)
obf2.lua нужно скомпилировать и выбрать в такой последовательности
196090


На выходе получаем файл obf2-inject.luac, закидываем в игру и вот скрин из игры:
1680438326831.png

(написал команду /crackme в чат)

Маленькая тонкость в obf2.lua:
При использовании инжектора можно убрать обманку
В debug.getinfo
Lua:
if f == 2 then
    return nil
end
Ведь мы именно *внедряемся/инжектимся* в файл, а не *склеиваемся*. Из-за этого уровень стека не изменяется.
 

Вложения

  • 1680437986876.png
    1680437986876.png
    3.4 KB · Просмотры: 1,414
  • obf2-inject.luac
    105.2 KB · Просмотры: 6
  • test-obf-x2-prot.luac
    104.4 KB · Просмотры: 7
  • obf2.lua
    1.4 KB · Просмотры: 16
  • obf.lua
    1.8 KB · Просмотры: 16

Jertshat

Участник
25
43
в подарок к гайду, исходник обфускатора, написан на питона. изучайте, развивайтесь.
коротко говоря, используются строки + таблицы.

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

так же деобфускатор, но реализация на расте.
 

Вложения

  • obfuscator (CrackMe).zip
    17.2 KB · Просмотры: 92
  • deobfuscator (CrackMe).zip
    1.7 KB · Просмотры: 91
Последнее редактирование:
  • Нравится
Реакции: chami

congic

Известный
330
99
в подарок к гайду, исходник обфускатора, написан на питона. изучайте, развивайтесь.
коротко говоря, используются строки + таблицы.

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

так же деобфускатор, но реализация на расте.
Как использовать ?