The Wild West Script | VM DEOBUFSCATOR

Roblox

Created by ???????

Features:

--[[
    wild_west_deobfuscator.lua

    made in about ~4 hours releasing this cus im mad and wild west owner refuses to pay me
    so enjoy updating ur shitty wild west exploits
    learn how to use this urself im not going to spoonfeed anyone

    LgouCi4KLgouCgozMDYxMjc4NTQ2NTgyNTY4OTYK
]]

-- stil not fully finished

local deobfuscation_settings = {
    enable_lower_optimizations = true;
}

-- utilities
local bit = require('bit32');

-- bytecode stream
local vanilla_dumper = { };
vanilla_dumper.sizet_size = 4;
vanilla_dumper.int_size = 4;

local bytecode_stream = { };
do
    bytecode_stream.__index = bytecode_stream;
    bytecode_stream.__tostring = function(self)
        return table.concat(self.buffer_);
    end

    function bytecode_stream.new()
        return setmetatable({
            buffer_ = { };
        }, bytecode_stream);
    end

    function bytecode_stream:write_int(number, int_size) -- big endian recursive order
        int_size = int_size or vanilla_dumper.int_size;

        self:write_byte(bit.band(number, 0xFF));

        if (int_size == 1) then
            return;
        end
        
        
        self:write_int(bit.rshift(number, 8), int_size - 1);

        return self;
    end

    function bytecode_stream:write_byte(byte)
        table.insert(self.buffer_, string.char(byte));

        return self;
    end

    local function mantissa_to_bytes(m)
        -- 54 bits
        -- floor(2^k * m) = 2^k m_53 + ... + m_{53 - k}
        -- 0 - 31 (32 bits), 32 - 54 (23 bits)
        local hi = math.floor(0x100000 * m)
        local lo = 0x100000 * m - hi
        lo = math.floor(lo * 0x100000000)
        hi = bit.band(0xfffff, hi)
        return lo, hi
      end
    
    function bytecode_stream:write_double(number)
        if number == 0 then self:write_int(0, 8) return end
        local m, e = math.frexp(number)
        m = 2*m
        e = e - 1
        local lo, hi_m = mantissa_to_bytes(m)
        -- 1 11 52
        -- hi:63    - sign
        -- hi:62-52 - exp
        -- hilo - 0 - man
        -- HI                               LO
        -- 00000000000000000000000000000000 00000000000000000000000000000000
        -- Seeeeeeeeeeemmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
        local hi_e = bit.lshift(bit.band(0x7ff, e + 1023), 20)
        local sign = bit.lshift(number < 0 and 1 or 0, 31)
        local hi = bit.bor(sign, bit.bor(hi_m, hi_e))
        self:write_int(lo, 4)
        self:write_int(hi, 4)
      end


    function bytecode_stream:write_string(str, sizet_size)
        sizet_size = sizet_size or vanilla_dumper.sizet_size;

        self:write_int(#str + 1, sizet_size);
        for i = 1, #str do
            self:write_byte(str:byte(i));
        end

        self:write_byte(0);

        return self;
    end
end

do
    function vanilla_dumper.dump_header(writer)
        writer:write_int(0x61754C1B)
            :write_byte(0x51)
            :write_byte(0)
            :write_byte(1)

            :write_byte(vanilla_dumper.int_size)
            :write_byte(vanilla_dumper.sizet_size)
            :write_byte(4)
            :write_byte(8) -- lua_Number default 8

            :write_byte(0);
    end

    function vanilla_dumper.dump_chunk(writer, chunk)
        local function write_code()
            writer:write_int(chunk.size_code); 
            for i = 1, chunk.size_code do
                local instruction = chunk.code[i];
                
                local data = 0;
                data = bit.bor(data, instruction.opcode);
                
                if (instruction.type == 'AsBx') then
                    data = bit.bor(data, bit.lshift(bit.band(instruction.a, 0xff), 6));
                    data = bit.bor(data, bit.lshift(instruction.sbx + 131071, 14));
                elseif (instruction.type == 'ABC') then
                    data = bit.bor(data, bit.lshift(bit.band(instruction.a, 0xff), 6));
                    data = bit.bor(data, bit.lshift(bit.band(instruction.c, 0x1ff), 6 + 8));
                    data = bit.bor(data, bit.lshift(bit.band(instruction.b, 0x1ff), 6 + 8 + 9));
                elseif (instruction.type == 'ABx') then
                    data = bit.bor(data, bit.lshift(bit.band(instruction.a, 0xff), 6));
                    data = bit.bor(data, bit.lshift(instruction.bx, 14));
                end

                writer:write_int(data);
            end
        end

        local function write_constants()
            writer:write_int(chunk.sizek);

            for i = 1, chunk.sizek do
                local value = chunk.constants[i];
                
                local value_tt = type(value);
                if (value_tt == 'string') then
                    writer:write_byte(4);
                    writer:write_string(value);
                elseif (value_tt == 'boolean') then
                    writer:write_byte(1);
                    writer:write_byte(value and 1 or 0);
                elseif (value_tt == 'number') then
                    writer:write_byte(3);
                    writer:write_double(value);
                else
                    writer:write_byte(0);
                end
            end
        end
        
        local function write_protos()
            writer:write_int(chunk.sizep);
            for i = 1, chunk.sizep do
                vanilla_dumper.dump_chunk(writer, chunk.protos[i]);
            end
        end

        local function write_debug()
            -- TODO

            --[[if (chunk.size_lineinfo ~= 0) then
                writer:write_int(chunk.size_code);
                for i = 1, chunk.size_code do
                    writer:write_int(chunk.code[i].line);
                end
            end]]
            writer:write_int(0);

            --[[writer:write_int(chunk.size_locvars);
            for i = 1, chunk.size_locvars do
                local locvar = chunk.locvars[i];
                
                writer:write_string(locvar.name);
                
                writer:write_int(locvar.start_pc);
                writer:write_int(locvar.end_pc);
            end]]

            writer:write_int(0);

            --[[writer:write_int(chunk.size_upvalues);
            for i = 1, chunk.size_upvalues do
                writer:write_string(chunk.upvalue_names[i]);
            end]]

            writer:write_int(0);
        end


        if (vanilla_dumper.is_top == true) then
            writer:write_string(chunk.name or 'empty');
            vanilla_dumper.is_top = false;
        else
            writer:write_string('');
        end
        
        writer:write_int(chunk.line_defined);
        writer:write_int(chunk.last_line_defined);

        writer:write_byte(chunk.nups);
        writer:write_byte(chunk.num_parameters);
        
        writer:write_byte(chunk.vararg_flag);
        writer:write_byte(chunk.max_stack_size);

        write_code();
        write_constants();
        write_protos();

        write_debug();
    end

    function vanilla_dumper.dump(chunk)
        local writer = bytecode_stream.new();
        
        vanilla_dumper.is_top = true;
        vanilla_dumper.dump_header(writer);
        vanilla_dumper.dump_chunk(writer, chunk);

        return tostring(writer);
    end
end

local function unpack_bits(Bit, Start, End) -- No tail-calls, yay.
    if End then -- Thanks to cntkillme for giving input on this shorter, better approach.
        local Res	= (Bit / 2 ^ (Start - 1)) % 2 ^ ((End - 1) - (Start - 1) + 1);

        return Res - Res % 1;
    else
        local Plc = 2 ^ (Start - 1);

        if (Bit % (Plc + Plc) >= Plc) then
            return 1;
        else
            return 0;
        end;
    end;
end;

local west_openum = {
    "MOVE",     
    "LOADK",     
    "LOADBOOL", 
    "LOADNIL",
    "GETUPVAL", 
    "GETGLOBAL",
    "GETTABLE",  
    "SETGLOBAL",
    "SETUPVAL", 
    "SETTABLE",  
    "NEWTABLE", 
    "SELF",
    "ADD",      
    "SUB",       
    "MUL",      
    "DIV",
    "MOD",      
    "POW",       
    "UNM",      
    "NOT",
    "LEN",      
    "CONCAT",    
    "JMP",      
    "EQ",
    "LT",       
    "LE",       
    "TEST",     
    "TESTSET",
    "CALL",     
    "TAILCALL",  
    "RETURN",   
    "FORLOOP",
    "FORPREP", 
    "TFORLOOP", 
    "SETLIST",  
    "CLOSE",
    "CLOSURE",  
    "VARARG",
    "LOADSERVICE",

    [64] = 'NOP'; -- 63
};

local west_optype = {
    'ABC',	'ABx',	'ABC',	'ABC';
    'ABC',	'ABx',	'ABC',	'ABx';
    'ABC',	'ABC',	'ABC',	'ABC';
    'ABC',	'ABC',	'ABC',	'ABC';
    'ABC',	'ABC',	'ABC',	'ABC';
    'ABC',	'ABC',	'AsBx',	'ABC';
    'ABC',	'ABC',	'ABC',	'ABC';
    'ABC',	'ABC',	'ABC',	'AsBx';
    'AsBx',	'ABC',	'ABC',	'ABC';
    'ABx',	'ABC',
    'ABx', -- LOADSERVICE

    [64] = 'ABC'; -- NOP
};

local west_undump;
do
    local west_signature = '27Lua';
    
      function west_undump(bytecode)
        local stream_position = 1;
        local read_sizet, read_int;
        local read1_function, read2_function;

        local function read_dword()
            local w, x, y, z = string.byte(bytecode, stream_position, stream_position + 3);
            
            stream_position = stream_position + 4;

            return (w * 16777216) + (x * 65536) + (y * 256) + z;
        end


        local function read_string(length)
            local result = string.sub(bytecode, stream_position, stream_position + length - 1);

            stream_position = stream_position + length;

            return result;
        end

        -- todo big endian readdouble so we dont need this scuffed bs
        local function read_double()
            local u9 = math.pow(2, 52);
            local u10 = math.pow(2, 48);
            local u11 = math.pow(2, 40);
            local u12 = math.pow(2, 32);
            local u13 = math.pow(2, 24);
            local u14 = math.pow(2, 16);
            local u15 = math.pow(2, 8);

            local p23 = false;
            local p22 = read_string(8);
            if p23 then
                p22 = p22:reverse();
            end;
            local v15 = string.byte(string.sub(p22, 1, 1));
            local v16 = string.byte(string.sub(p22, 2, 2));
            local v17 = string.byte(string.sub(p22, 3, 3));
            local v18 = string.byte(string.sub(p22, 4, 4));
            local v19 = string.byte(string.sub(p22, 5, 5));
            local v20 = string.byte(string.sub(p22, 6, 6));
            local v21 = string.byte(string.sub(p22, 7, 7));
            local v22 = string.byte(string.sub(p22, 8, 8));
            local v23;
            if v15 >= 128 then
                v23 = 1;
            else
                v23 = 0;
            end;
            local v24 = v15 % 128 * 16 + math.floor(v16 / 16);
            local v25 = v16 % 16 * u10 + v17 * u11 + v18 * u12 + v19 * u13 + v20 * u14 + v21 * u15 + v22;
            if v24 == 2047 then
                if v25 == 0 then
                    return math.pow(-1, v23) * math.huge;
                end;
                if v25 == u9 - 1 then
                    return (0 / 0);
                end;
            end;
            if v24 == 0 then
                return math.pow(-1, v23) * math.pow(2, v24 - 1023) * (v25 / u9);
            end;
            return math.pow(-1, v23) * math.pow(2, v24 - 1023) * (v25 / u9 + 1);
        end

        local function read_byte()
            local current = bytecode:byte(stream_position);
            stream_position = stream_position + 1;
                
            return current;
        end

        local function read_lua_string()
            local length = read1_function();

            if (length == 0) then
                return;
            end

            local result = string.sub(bytecode, stream_position, stream_position + length - 1);

            stream_position = stream_position + length;

            return result;
        end

        local function read_short()
            local w, x = string.byte(bytecode, stream_position, stream_position + 1);
            stream_position = stream_position + 2;

            return (w * 256) + x;
            --return read_sequence(2);
        end
        
        local function read_qword()
            return read_dword() * 4294967296 + read_dword();
        end

        local readsize_dispatch_table = {
            [1] = read_byte;
            [2] = read_short;
            [4] = read_dword;
            [8] = read_qword;
        }

        local function read_chunk()
           local code = { };
           local constants = { };
           local protos = { };

           -- recovering debug info
           local lineinfo = { };
           local locvars = { };
           local upvalue_names = { };

           local chunk = {
               lineinfo = lineinfo;
               locvars = locvars;
               upvalue_names = upvalue_names;

               constants = constants;
               code = code;
               protos = protos;
           };

           -- init
           chunk.name = string.sub(read_lua_string(), 1, -2);

           chunk.line_defined = read1_function();
           chunk.last_line_defined = read1_function();

           chunk.nups = read_byte();
           chunk.num_parameters = read_byte();
           
           chunk.vararg_flag = read_byte();

           chunk.max_stack_size = read_byte();
           
           -- decode instructions
           chunk.size_code = read1_function();
          

           for i = 1, chunk.size_code do
                local instruction = read_dword();
                -- same insn format as vanilla lua with some changes

                local opcode = instruction % 64;
                local type = west_optype[opcode + 1];

                -- unpack_Bits slow
                
                local new_instruction = { 
                    opcode = opcode,
                    type = type,
                    line = -1, -- TODO propagate
                    a = unpack_bits(instruction, 7, 14);
                };

                if (type == 'ABC') then
                    new_instruction.b = unpack_bits(instruction, 24, 32);
                    new_instruction.c = unpack_bits(instruction, 15, 23);
                elseif (type == 'ABx') then
                    new_instruction.bx = unpack_bits(instruction, 15, 32);
                elseif (type == 'AsBx') then
                   new_instruction.sbx = unpack_bits(instruction, 15, 32) - 131071;
                end

                -- emplace to instructions
                code[i] = new_instruction;
           end
            
           -- decode constants
           chunk.sizek = read1_function();
           for i = 1, chunk.sizek do
                local kst_type = read_byte();
                local kst_result;

                if (kst_type == 1) then -- LUA_TBOOL
                    kst_result = (read_byte() ~= 0);
                elseif (kst_type == 0) then -- LUA_TNIL
                    kst_result = nil;
                elseif (kst_type == 3) then -- LUA_TNUMBER
                    kst_result = read_double();
                elseif (kst_type == 4) then -- LUA_TSTRING
                    kst_result = string.sub(read_lua_string(), 1, -2);
                end

                --print(kst_result)
                constants[i] = kst_result;
           end

           -- decode protos
           chunk.sizep = read1_function();
           for i = 1, chunk.sizep do
              protos[i] = read_chunk();
           end

          -- decode debug info (stripped atm)
           do           
                chunk.size_lineinfo = read1_function();

                print(chunk.size_lineinfo, chunk.size_code);

                for i = 1, chunk.size_lineinfo do
                    chunk.code[i].line = read1_function(); -- propagate line
                    --lineinfo[i] = read1_function();
                end

                chunk.size_locvars = read1_function();
                for i = 1, chunk.size_locvars do
                    locvars[i] = {
                        name = string.sub(read_lua_string(), 1, -2);
                        start_pc = read1_function();
                        end_pc = read1_function();
                    };
                end 

                chunk.size_upvalues = read1_function();
                for i = 1, chunk.size_upvalues do
                    upvalue_names[i] = string.sub(read_lua_string(), 1, -2);
                end
           end
           
           return chunk;
        end

        -- read header

        assert(read_string(4) == west_signature, '[west_transpiler]: west bytecode expected (header)');
        
        assert(read_byte() == 0x51, 'expected lua 5.1');

        assert(read_byte() == 1, 'invalid format'); -- format

        read_byte(); -- endianess

        local read1_size = read_byte();
        local read2_size = read_byte();
        
        read1_function = readsize_dispatch_table[read1_size];
        read2_function = readsize_dispatch_table[read2_size];

        assert(read_string(3) == '48');

        return read_chunk();
    end
end


-- instruction reference wrapper
local instruction_refwrapper = { };
do
    instruction_refwrapper.__index = instruction_refwrapper;

    function instruction_refwrapper.create_wrapper(value)
        return setmetatable({
            value_ = value; -- pointer to old instruction
            next_ = nil;
            prev_ = nil;
            id_ = -1; -- for topological ordering
            jmp_sucessor_ = nil;
            sucessor_ = nil; -- alternative sucessor
        }, instruction_refwrapper);
    end

    -- transforms instruction list into circular doubly linked list
    function instruction_refwrapper.transform_wrappers(list)
        local node = instruction_refwrapper.create_wrapper(list[1]);
        local temp = node;

        for i = 2, #list do
            node = node:insert(list[i]);
        end

        return temp;
    end

    function instruction_refwrapper:transform_array()
        local result = { };

        do
            local current = self;
            while (current) do
                local value = current.value_;
                table.insert(result, value);
                current = current.next_;
            end
        end

        return result;
    end

    function instruction_refwrapper:pop_backward()
        local result = self.prev_;
        self.prev_.next_ = self.next_;
        return result;
    end 

    function instruction_refwrapper:pop_forward()
        local result = self.next_;
        self.prev_.next_ = self.next_;
        return result;
    end 

    function instruction_refwrapper:insert(value)
        local node = instruction_refwrapper.create_wrapper(value);

        if (self.next_) then
            self.next_.prev_ = node;
            node.next_ = self.next_;
        end

        node.prev_ = self;
        self.next_ = node;

        return node;
    end 

    -- getters & setters
    function instruction_refwrapper:get_value()
        return self.value_;
    end
end

-- set of conditional branch instructions (expect loadbool)
local conditional_set = {
    [23] = true; -- EQ
    [24] = true; -- LT
    [25] = true; -- LE
    [31] = true; -- FORLOOP
    [33] = true; -- TFORLOOP
    [26] = true; -- TEST
    [27] = true; -- TESTSET
};

local function map_pcs(wrapper)
    local pc_map = { };

    do
        local pc = 1;
        local o_wrapper = wrapper;
        while (o_wrapper) do
            pc_map[pc] = o_wrapper;
            o_wrapper = o_wrapper.next_;
            pc = pc + 1;
        end
    end

    return pc_map;
end

local function map_instructions(wrapper)
    local pc_map = { };

    do
        local pc = 1;
        local o_wrapper = wrapper;
        while (o_wrapper) do
            pc_map[o_wrapper] = pc;
            o_wrapper = o_wrapper.next_;
            pc = pc + 1;
        end
    end

    return pc_map;
end


local function populate_sucessors(wrapper)
    --local pc_map = { };

    local pc_map = map_pcs(wrapper);

    do
        local pc = 1;
        while (wrapper) do
            local value = wrapper.value_;
            if (value.opcode == 22) then -- JMP ONLY SUPPORTED RN
                local target_wrapper = pc_map[value.sbx + pc + 1];
                if (target_wrapper ~= nil) then
                    wrapper.jmp_sucessor_ = target_wrapper;
                end
            elseif (value.opcode == 32 or value.opcode == 31) then -- forprep & forloop
                local target_wrapper = pc_map[value.sbx + pc + 1];
                if (target_wrapper ~= nil) then
                    wrapper.sucessor_ = target_wrapper;
                end
            end

            --[[elseif (conditional_set[value.opcode]) then
                local target_wrapper = pc_map[pc + 1];
                if (target_wrapper ~= nil) then
                    wrapper:set_sucessor(target_wrapper);
                end
            end]]

            wrapper = wrapper.next_;
            pc = pc + 1;
        end
    end
end


local instruction_toposort = { };
do
    instruction_toposort.__index = instruction_toposort;

    function instruction_toposort.create(begin)
        return setmetatable({
            begin_ = begin;

            visited_ = { }; -- visited nodes
            queue_ = { }; -- BFS queue
            order_ = { }; -- post order of instructions
        }, instruction_toposort);
    end

    function instruction_toposort:cleanup()
        local wrapper = self.begin_;

        while (wrapper) do
            if (not self.visited_[wrapper]) then
                wrapper = wrapper:pop_backward();
            else
                wrapper = wrapper.next_;
            end
        end
    end

    -- time complexity: O(v + e)
    function instruction_toposort:run()
        table.insert(self.queue_, self.begin_);

        while (#self.queue_ > 0) do
            local current = table.remove(self.queue_);
            table.insert(self.order_, current);
            self.visited_[current] = true;

            while (current) do
                local value = current.value_;

                -- handler
                local sucessor = current.jmp_sucessor_;
                if (sucessor ~= nil) then
                    -- not conditional fallthough?
                    if (not conditional_set[current.prev_.value_.opcode]) then
                        local current_sucessor = sucessor.jmp_sucessor_;
                        
                        -- jmp 1 mostly appears on conditional exprs (ex bin loadbool), skip out non conditional jmp 1's (coulda been optimized after sort aswell)
                        if (current.value_.sbx == 1 and not current_sucessor) then
                            table.insert(self.queue_, sucessor);
                            goto bfs_jumpback;
                        end
                        
                        while (current_sucessor) do -- while (current and current.opcode ~= 22)
                            if (current_sucessor.value_.opcode ~= 22) then -- JMP
                                table.insert(self.queue_, current_sucessor);
                                goto bfs_jumpback;
                            end

                            current_sucessor = current_sucessor.jmp_sucessor_;
                        end
                    end
                end

                if (not self.visited_[current]) then
                    table.insert(self.order_, current);
                    self.visited_[current] = true;
                end

                --table.insert(queue, current);
                current = current.next_;
            end

            ::bfs_jumpback::
        end
    end

    function instruction_toposort:get_order()
        return self.order_;
    end
end

-- todo opcode transformers
--[[
    loadservice transformer (PSUEDOINSTRUCTION):
        getglobal LOADSERVICE_A, KST('game')
        self LOADSERVICE_A, LOADSERVICE_A, KST('GetService')
        loadk LOADSERVICE_A + 2, LOADSERVICE_BX
        call LOADSERVICE_A, 3, 2 
        
   for NOP just remove instruction
]]

-- chunk methods
local MAX_REG = 250;

local function allocate_registers(chunk, begin, offset, registers)
    -- todo, and handle param

    chunk.max_stack_size = chunk.max_stack_size + registers;
end

local function free_registers(chunk, begin, offset, registers)
    -- todo
end

local function get_or_create_constant(chunk, kst)
    for i = 1, chunk.sizek do
        if (chunk.constants[i] == kst) then
             return i - 1;
        end
    end

    table.insert(chunk.constants, kst);
    chunk.sizek = chunk.sizek + 1;

    return chunk.sizek - 1;
end

local opcode_transformer = { };
do
    opcode_transformer.__index = opcode_transformer;

    function opcode_transformer.create(chunk, begin)
        return setmetatable({
            chunk_ = chunk;
            begin_ = begin;
        }, opcode_transformer);
    end

    function opcode_transformer:pass()
        local wrapper = self.begin_;
        while (wrapper) do
            local value = wrapper.value_;

            if (value.opcode == 38) then -- LOADSERVICE PSUEDOINSTRUCTION
                -- required constants
                local game_kst = get_or_create_constant(self.chunk_, 'game');
                local getservice_kst = get_or_create_constant(self.chunk_, 'GetService');
                
                local offset = value.a;

                wrapper = wrapper:pop_backward();

                local current_line = (wrapper.value_.line or -1) + 1;

                wrapper = wrapper:insert{
                    opcode = 5;
                    type = 'ABx';
                    a = offset;
                    line = current_line;
                    bx = game_kst;
                }:insert{
                    opcode = 11;
                    type = 'ABC';
                    a = offset;
                    b = offset;
                    line = current_line;
                    c = 256 + getservice_kst;
                }:insert{
                    opcode = 1;
                    type = 'ABx';
                    a = offset + 2;
                    line = current_line;
                    bx = value.bx;
                }:insert{
                    line = current_line;
                    opcode = 28;
                    type = 'ABC';
                    a = offset;
                    b = 3;
                    c = 2;
                };

                -- allocate registers 
                --allocate_registers(self.chunk_, self.begin_, offset, 2);

               -- wrapper = result;
            elseif (value.opcode == 63) then -- NOP
                wrapper = wrapper:pop_backward();
            else -- continue execution
                wrapper = wrapper.next_;
            end
        end
    end
end


local function relink_previous_wrappers(wrapper)
    local o_prev = nil;
    while (wrapper) do
        wrapper.prev_ = o_prev;
        o_prev = wrapper; 
        wrapper = wrapper.next_;
    end
end

local function fix_target_branch_offsets(wrapper)
    local instruction_pc_map = map_instructions(wrapper);

    local current_pc = 1;
    while (wrapper) do
        local sucessor = wrapper.jmp_sucessor_ or wrapper.sucessor_;
        if (sucessor) then
            local pc = instruction_pc_map[sucessor];
            if (pc) then
                wrapper.value_.sbx = pc - current_pc - 1;
            end
        end

        wrapper = wrapper.next_;
        current_pc = current_pc + 1;
    end
end

local varardic_opcodes = {
    [28] = true;
    [29] = true;
    [30] = true;
    [34] = true;
};

local function decode_chunk(chunk, wrapper)
    local string_kst = get_or_create_constant(chunk, 'string');
    local char_kst = get_or_create_constant(chunk, 'char') + 256;

    local worklist = { };
    --local decoded_set = { };

    local function process_decode(start, current_wrapper)
        local call_stack_track = 0;

        local result = { };
        while (current_wrapper) do
            current_wrapper = current_wrapper.next_;
            call_stack_track = call_stack_track + 1;

            local current_value = current_wrapper.value_;
            
            if (current_value.opcode ~= 1) then -- LOADK
                break;
            end

            if (call_stack_track + start ~= current_value.a) then
                break;
            end

            local constant_value = chunk.constants[current_value.bx + 1];
            
            result[call_stack_track] = constant_value;
        end

        result = string.char(unpack(result));

        return current_wrapper, result;
    end

    local o_wrapper = wrapper;
    while (o_wrapper) do
        local value = o_wrapper.value_;


        local v = value;
         if (v.type == 'ABC') then
               -- print(west_openum[v.opcode + 1], v.a, v.b, v.c);
            elseif (v.type == 'AsBx') then
                --print(west_openum[v.opcode + 1],v.a, v.sbx);
            elseif (v.type == 'ABx') then
                --print(west_openum[v.opcode + 1],v.a, v.bx);
            end

        if (value.opcode == 5 and value.bx == string_kst) then  -- getglobal
            local next_wrapper = o_wrapper.next_;
            local next_value = next_wrapper.value_;
            if (next_value.opcode == 6 and next_value.a == value.a and next_value.b == value.a) then -- gettable A A
                if (next_value.c > 255 and next_value.c == char_kst) then
                    -- process
                    local continue_wrapper, result = process_decode(next_value.a, next_wrapper);

                    local new_constant = get_or_create_constant(chunk, result);

                    local loadconstant_new = instruction_refwrapper.create_wrapper({
                        opcode = 1;
                        type = 'ABx';
                        line = value.line;
                        a = value.a;
                        bx = new_constant;
                    });

                    o_wrapper.value_ = loadconstant_new.value_;

                    o_wrapper.next_ = continue_wrapper.next_;

                    continue_wrapper.prev_ = o_wrapper;

                    table.insert(worklist, o_wrapper);
                    
                    --decoded_set[o_wrapper] = true;

                    --wrapper.prev_.next_ = loadconstant_new;
                    --loadconstant_new.next_ = continue_wrapper.next_;
                    --loadconstant_new.prev_ = wrapper.prev_;

                    --wrapper = wrapper.next_;
                end
            end
        end

        o_wrapper = o_wrapper.next_;
    end

    -- fuck this shit im lazy
    relink_previous_wrappers(wrapper);

    -- we can assume that wild west replaces string constants to an extended string.char call (on intermediate level)
    -- because B is going to be zero on TOP instructions where it has been called, hence we need
    -- to fix B=0 return/setlist/tail/call instructions (reset L->top so can use only 1 return value)

    while (#worklist > 0) do
        local wrapper = table.remove(worklist, 1);
        local old_reg = wrapper.value_.a;

        wrapper = wrapper.next_;
        while (wrapper) do
            local value = wrapper.value_;
            if (varardic_opcodes[value.opcode]) then
                if (value.c == 0) then
                    break;
                end

                if (value.b == 0) then
                    if (old_reg < value.a) then
                        -- todo
                        break;
                    end

                    -- top is dynamic?
                    --local previous = wrapper.prev_;
                    --if (settop_opcodes[previous.value_.opcode]) then
                    --    if (previous.value_.c == 0) then
                    --       break;
                    --    end
                    --end

                    value.b = old_reg - value.a + 1; -- reset top to minimal parameter value
                    break;
                end
            elseif (wrapper == worklist[1]) then
                break;
            end

            wrapper = wrapper.next_;
        end
    end
end


local function controlvar_add_optimization_pass(chunk, wrapper)
    local kst_1 = get_or_create_constant(chunk, 1) + 256;

    local num_additions = 0;
    local add_wrapper = nil;

    while (wrapper) do
        local value = wrapper.value_;
        if (value.opcode == 12 and value.a == value.b and value.c == kst_1) then
            if (not add_wrapper) then
                add_wrapper = wrapper;
            end

            num_additions = num_additions + 1;
        elseif (add_wrapper) then
            local kst = get_or_create_constant(chunk, num_additions);
            
            add_wrapper.value_.c = 256 + kst;

            add_wrapper.next_ = wrapper;

            wrapper.prev_ = add_wrapper;

            -- reset variables
            add_wrapper = nil;
            num_additions = 0;
        end

        wrapper = wrapper.next_;
    end
end

local function transpile_chunk(chunk)
    -- generate reference wrappers
    local wrappers = instruction_refwrapper.transform_wrappers(chunk.code);

    -- extract jmp sucessors
    populate_sucessors(wrappers);

    if deobfuscation_settings.enable_lower_optimizations then 
        -- fix string.char
        decode_chunk(chunk, wrappers);

        -- optimize control variables
        controlvar_add_optimization_pass(chunk, wrappers);
    end

    -- perform topological order (min nodes, determined by unreachable PCs)
    local sorter = instruction_toposort.create(wrappers);
    
    sorter:run();

    -- run cleanup
    sorter:cleanup();

    -- relink previous wrappers after topological sort
    relink_previous_wrappers(wrappers);
    
    -- transform custom opcode instructions
    local transformer_pass = opcode_transformer.create(chunk, wrappers);
    transformer_pass:pass();

    -- fix branch offsets
    fix_target_branch_offsets(wrappers);

    -- transform wrappers -> array
    chunk.code = instruction_refwrapper.transform_array(wrappers);

    -- fix chunk values after optimizations
    chunk.size_code = #chunk.code;

    for i = 1, chunk.sizep do
        transpile_chunk(chunk.protos[i]);
    end
end

-- main
do
    local input_file = io.open('test.bin', 'rb');   
    local input_bytecode = input_file:read('*a');
    input_file:close();

    local chunk = west_undump(input_bytecode);

    transpile_chunk(chunk);

    local result = vanilla_dumper.dump(chunk);
    
    local output_file = io.open('output.luac', 'wb');
    output_file:write(result);
    output_file:close();

    print('done');
end

ENJOY!

 

Warning: DO NOT DOWNLOAD anything from this page, you’re only here to copy the script or Get it from Pastebin!

About Us

What is arponag.xyz? arponag.xyz is a website focused on releasing safe exploits, we only release the best and most trusted exploits on our website, guaranteed to satisfy you. Contact us [email protected]

Safety

Is this safe to use? Everything on the website has been scanned and tested by professionals and the community, we only release the best exploits for our users, so we make sure that our exploits won’t harm your devices, everything here is clean.

Copyright © 2018 – 2020 Arpon AG | All rights reserved |
Privacy Policy | Terms of Service