Cracking Lua program using code injection

Hello, this is the writeup of a small crackme I made.
Crackme download links:
32 bit : https://drive.google.com/open?id=0B7U3AsTA9UVfQzVGbHl0cTRTa00
64 bit : https://drive.google.com/open?id=0B7U3AsTA9UVfYTYwZWt3dER0ems
Source code : https://pastebin.com/v3EfAtLu


Method 1 (The recommended way)

After reading the hints (there is an obvious flaw in the source code), we start thinking, what are the possible security flaws in a Lua program? After looking at the Wikipedia page of Lua, we find an interesting section : the C API, we find a code snippet that executes code in the context of the program.

#include <stdio.h>
#include <lua.h> //Lua main library (lua_*)
#include <lauxlib.h> //Lua auxiliary library (luaL_*)

int main(void)
{
    //create a Lua state
    lua_State *L = luaL_newstate();

    //load and execute a string
    if (luaL_dostring(L, "function foo (x,y) return x+y end")) {
        return -1;
    }
    lua_close(L);
    return 0;
}


One idea that we get is : to use it to inspect the environment variables, maybe we'll find something interesting.
From the Lua manual : luaL_dostring is defined as the following macro : (luaL_loadstring(L, str) || lua_pcall(L, 0, LUA_MULTRET, 0))

First, we will need the lua_State, getting it is easy, just put a breakpoint on any lua_x or luaL_x function that gets called often (like lua_gettop) and log its first argument ([ESP+4] if 32bit, rcx if 64bit).

We will crack the 64bit version, there isn't a big difference (just a bit in assembly, the calling convention etc.)













lua_State = 0xE23688


Now, we write some assembly code that executes Lua code in the context of our crackme:
We will use lua_callk instead of lua_pcallk (pcall stands for protected call, it does error handling, but we are not going to raise exceptions anyway)


sub rsp, 28 // shadow space
mov rcx, 0xE23688 // lua_state
mov rdx, code // pointer to our Lua code, as a string
call luaL_loadstring

mov rcx, 0xE23688
xor rdx, rdx
xor r8, r8 // number of arguments and number of returned values
call lua_callk

xor rcx, rcx
movabs rax, RtlExitUserThread
call rax

add rsp, 28
ret

code:
db 'for i,v in pairs(_G) do print(i,v) end',0




As we see, the console is visible, so we can just print stuff, if it wasn't, we would either write to files (io.open, file:write etc.) or try things like AllocConsole.



We search for some free memory in the main module, and check the memory map to ensure that it's executable









We inject our code there, and remplace the code label with its address







Now, we create a new thread to execute our injected code (we use the createthread command in x64dbg, we could use createRemoteThread from a separate program):





Wow, it printed all the global variables, we immediately notice secret1337, all the other variables are the lua builtin libraries/functions, plus the iup library (for graphical interface).


We change our Lua code to : for i,v in pairs(secret1337) do print(i,v) end  to iterate over the secret table, and execute our injected code once again

Nothing is printed! after reading a bit about Lua tables, we find out that they can be associated with metatables, and that these metatables can define their behaviour (how they should respond to the indexing operation, to arithmetic operations, comparison etc.), we test something simple:



print(getmetatable(secret1337))







Bingo! our table is associated with a metatable, and it's not protected (it can be protected with __metatable metamethod), what we will do now : check how secret1337 will respond to the index operation, the __index metamethod can be either a function or a table, our code will look like this:

if type(getmetatable(secret1337).__index) == 'table' then for i,v in pairs(getmetatable(secret1337).__index) do print(i,v) end else print"it's not a table" end









Awesome, it's a table, and what is that? secret1337.key = 'YrfnKownvYnb'

We try it in our crackme, and it works!






Method 2 (The more generic way) 

When I made this crackme, I wanted people to use the first method to solve it (I gave the hint about the obvious flaw in the source code), I could have made this one harder by adding fake strings that look like the luac header/EOF everywhere in the executable, but I didn't, so this method is still easy to do.

First, we inspect the exe file with a Hex Editor, no source code found, we can only find some junk strings etc., it's probably a compiled lua file (luac) that is embedded in the executable, we compile a few simple lua programs (we can take examples from the net), and inspect the luac files




We notice that they all start with "\eLuaS" and end with '_ENV', using this Linux command : strings crackme.exe | grep LuaS , we can see that LuaS appears once in the executable.

To dump it, we will write a simple Ruby script that 1) finds "\eLuaS" in the file, and dumps all the memory till it encounters "_ENV", if it encounters more than one "_ENV" after "\eLuaS", it will dump all of them as separate files (we will check which are valid luac files after that).

header = "\x1b\x4c\x75\x61\x53";

f = File.open('crackme.exe','rb');

data = f.read;
f.close;
ends = data.each_char.each_cons(4).with_index.select{|c,i| c.join == '_ENV'}.map(&:last)
d = Dir.mkdir('out') unless Dir.exists?('out');
fileind = 1;
p ends
data.each_char.each_cons(5).with_index{|sl,i|
    if sl.join == header
        ends.select{|ind| ind > i}.each{|e|
            File.open('out/out' + fileind.to_s, 'wb') do|g|
                g.write(data[i..e+3]);
            end

            fileind += 1;
        }
    end
}


After dumping (It found one file only), it's very easy using luadec: luadec out1





One interesting part of our source code is this :

check.action = function(self)
  if inp.value == (load(("ret" .. "urn %c%s%c%s%x.%s"):format(115, "ec", 114, "et", 4919, var)))() then
    --some obfuscated code calling iup.Message
  else
    --some other obfuscated code that calls iup.Message
  end
end


that condition is equivalent to inp.value == load('return secret1337.key')(), looking at secret1337, it's an empty table, with a metatable having __index = { [var] = ("%c%c%c" .. "%c%c%c%c" .. "%c%c%c%c%c"):format(89, 114, 102, 110, 75, 111, 119, 110, 118, 89, 110, 98)}

var is equal to 'key', so it is very easy from here, take 89, 114, 102, 110, 75, 111, 119, 110, 118, 89, 110, 98, convert each of them to chr, and join

puts [89, 114, 102, 110, 75, 111, 119, 110, 118, 89, 110, 98].map(&:chr).join

#=> YrfnKownvYnb



Easy, no?

Commentaires

Posts les plus consultés de ce blog

HackINI 2018 : Some Writeups

Himayatic rev_400 Writeup (.NET Crackme)