Hooking Lua Part 3

Introduction
After learning a bit about how Syrian Warfare handles Lua context, I wanted to figure out how I could execute my own scripts from within the game.

Enabling io
The game does not use the Lua io library, so I modified my injected DLL to have the capability to execute luaopen_io() when F5 key is pressed. I tested the newly enabled io library by executing the following from console:

dump = io.open("testing_io_lua.txt","w")
dump:write("Wrote from lua")
dump:flush()
dump:close()

The file appeared in the game directory with the correct text.

Adding new scripts
I tried to execute dofile() and loadfile() from the command console, but I kept on getting file not found errors. To debug this, I put a breakpoint on lua_load. This led me to the discovery that Galileo loads up scripts from scripts/ and scripts/lua. Looking at strings in SyrianWarefare.exe, I noticed references to “scripts/triggers.lua” and “scripts/global_map.lua.”

global_map.lua uses dofile() to read and execute other lua files:

--************************************************************************
--* Вспомогательные функции и константы                                  *
--************************************************************************
-- utility variables
-- constants
SCRIPTS_PATH = "scripts/lua/"
dofile(SCRIPTS_PATH.."constants.lua")
-- соответствие объекта спауну
dofile(SCRIPTS_PATH.."spawns.lua")
-- соответстиве объекта резервам
dofile(SCRIPTS_PATH.."reserves.lua")
-- вспомогательные функции
dofile(SCRIPTS_PATH.."tools.lua")

I put test.lua into the main.pak zip file and ran the game from Steam. The game started up normally. When I executed the following on console:
dofile(SCRIPTS_PATH..”test.lua”)
the following error printed in DebugView:
[11060] mll::debug::exception: [ml_encrypted_zip: unknown zlib error while inflating]

It appears that the game expects all the files in the zip to be encrypted. Since I know the password used to encrypt the game files, I used 7zip to append the file to main.pak:

$ 7z.exe a main.pak test.lua -pm,nw0rdk1s;ldscj

Conclusion
By appending a Lua script to the game zip files and forcing the game to load Lua io library, I was able to successfully execute test.lua from inside the game. The approach could be used to add and execute custom scripts. The source code for the injected DLL can be found here:
https://github.com/sbobovyc/SyrianWarfare_lua_hacks

Hooking Lua Part 2

Introduction

After learning about how to integrate Lua into a C program and some Lua internals, I wanted to put this knowledge to hacking around in a game uses Lua. I selected Syrian Warfare for this purpose. Since the code is still in a state of flux, only small snippets will be shown to make points. A link to the full source will be made available in a later post.

Exploration

As in any of my reverse-engineering endeavors, I searched the binaries for interesting strings:

$ strings SyrianWarfare.exe  | grep -i lua
autoexec.lua
test_autoexec.lua
scripts/global_map.lua
scripts/triggers.lua
$ strings lua.dll  | grep -i lua  | grep Copyright
$Lua: Lua 5.0.2 Copyright (C) 1994-2004 Tecgraf, PUC-Rio $

The game actually provides an interactive console in which Lua code can be executed.
sw_lua_version__console.jpg

I started the game in a debugger and put a breakpoint on lua_open. This function is called from two different places while the game starts up: galileo.dll and world2.dll.

00772E4C | call dword ptr ds:[<&lua_open>]                                       |
00772E52 | push eax                                                              |
00772E53 | mov dword ptr ds:[esi+4],eax                                          |
00772E56 | call dword ptr ds:[<&luaopen_base>]                                   |
00772E5C | mov ecx,dword ptr ds:[esi+4]                                          |
00772E5F | push ecx                                                              |
00772E60 | call dword ptr ds:[<&luaopen_string>]                                 |
00772E66 | mov edx,dword ptr ds:[esi+4]                                          |
00772E69 | push edx                                                              |
00772E6A | call dword ptr ds:[<&luaopen_table>]                                  |
00772E70 | mov eax,dword ptr ds:[esi+4]                                          |
00772E73 | push eax                                                              |
00772E74 | call dword ptr ds:[<&luaopen_math>]                                   |
00772E7A | mov edi,dword ptr ds:[esi+4]                                          |
00772E7D | call galileo.772CD0                                                   |
00772E82 | mov edi,dword ptr ds:[esi+4]                                          |
00772E85 | push galileo.77F6D8                                                   | 77F6D8:"galileo.console_impl"
00772E8A | push edi                                                              |
00772E8B | call dword ptr ds:[<&lua_pushstring>]                                 |
00772E91 | push esi                                                              |
00772E92 | push edi                                                              |
00772E93 | call dword ptr ds:[<&lua_pushlightuserdata>]                          |
00772E99 | push FFFFD8F0                                                         |
00772E9E | push edi                                                              |
00772E9F | call dword ptr ds:[<&lua_settable>]                                   |
00772EA5 | add esp,28                                                            |
00772EA8 | push esi                                                              |
00772EA9 | call galileo.772FD0                                                   |
00772EAE | mov ecx,dword ptr ss:[esp+10]                                         |
00772EB2 | pop edi                                                               |
00772EB3 | mov eax,esi                                                           |
00772EB5 | pop esi                                                               |
00772EB6 | mov dword ptr fs:[0],ecx                                              |
00772EBD | add esp,14                                                            |
00772EC0 | ret 8                                                                 |
08170965 | call dword ptr ds:[<&lua_open>]                                       |
0817096B | push eax                                                              |
0817096C | mov dword ptr ds:[edi+10],eax                                         |
0817096F | call dword ptr ds:[<&luaopen_base>]                                   |
08170975 | mov eax,dword ptr ds:[edi+10]                                         |
08170978 | add esp,4                                                             |
0817097B | push eax                                                              |
0817097C | call dword ptr ds:[<&luaopen_math>]                                   |
08170982 | mov esi,dword ptr ds:[edi+10]                                         |
08170985 | add esp,4                                                             |
08170988 | push world2.82580B0                                                   | 82580B0:"Compound = {}
Compound.mt = {}
function Compound.Add (a,b)
  local res = CompoundDomain{a,b,'+'}
  return res
end
function Compound.Sub (a,b)
  local res = CompoundDomain{a,b,'-'}
  return res
end
Compound.mt.__add = Compound.Add
Compound.mt.__sub = Compound.Sub
"
0817098D | push esi                                                              |
0817098E | call dword ptr ds:[<&lua_dostring>]                                   |
08170994 | call world2.817B4C0                                                   |
08170999 | call world2.8178A00                                                   |
0817099E | push FFFFD8EF                                                         |
081709A3 | push esi                                                              |
081709A4 | call dword ptr ds:[<&lua_pushvalue>]                                  |
081709AA | push 0                                                                |
081709AC | push world2.827D5A0                                                   | 827D5A0:&"Material"
081709B1 | push 0                                                                |
081709B3 | push esi                                                              |
081709B4 | call dword ptr ds:[<&luaL_openlib>]                                   |
081709BA | push FFFFFFFE                                                         |
081709BC | push esi                                                              |
081709BD | call dword ptr ds:[<&lua_settop>]                                     |
081709C3 | add esp,28                                                            |
081709C6 | mov eax,edi                                                           |
081709C8 | pop esi                                                               |
081709C9 | pop ecx                                                               |
081709CA | ret 4                                                                 |

At this point I decided to employ the following strategy. Use an injected DLL to hook and instrument Lua library functions and figure out how the game uses these different Lua contexts. The injected DLL uses GetModuleHandle to get a handle to the Lua DLL and GetProcAddress to get addresses of the following functions:
lua_open
lua_close
luaopen_io
lua_dostring
lua_dofile
lua_gettop
lua_tostring
lua_settop
lua_next
lua_tonumber
lua_topointer

lua_pushstring
lua_gettable
lua_pushnil

lua_isstring
lua_isnumber
lua_type

Using Minhook, I detoured lua_open, lua_close, and lua_gettop. The latter function is frequently called and is useful in discovering Lua contexts that exist before the DLL is injected. When a new Lua context is created or discovered, it is saved to an unordered_set.

int hooked_lua_gettop(lua_State *L) {
    if (!lua_States.count(L)) { // if state is already recorded, don't debug print
        wsprintf(debug_string, _T("In lua_gettop, lua_State 0x%p"), L);
        OutputDebugString(debug_string);
        lua_States.insert(L);
    }   
    return orig_gettop(L);
}

Instrumenting Lua function confirmed that there are at least two lua states being used. One is created by world2.dll and is created on save game load and closed when a new save game is loaded.

[1708] In lua_close, lua_State 0x05D269D8
[1708] In lua_open, lua_State 0x05D26970
[1708] In lua_gettop, lua_State 0x05D26D18
[1708] In lua_gettop, lua_State 0x05D26BE0

Another state is created when the game starts up in galileo.dll and has the “game” namespace which contains interesting functions that I will cover in another post. To make learning about game internals easier, I added various debugging functionality to the injected DLL that could be activated with key presses. Pressing F8 key prints out all the known Lua contexts. Since there are multiple contexts, I found it useful to introduce the notion of “currently selected context.” The variable game_L holds the address of the currently selected context and by pressing Shift+F7 the next known context is made the currently selected context. Also, I added a helper function that prints out Lua tables using DebugString() when Shift+F8 key is pressed. Calling this function on the “_G” table gives me a list of global functions, tables and strings. From main menu to right after a save game was loaded, the following Lua contexts were found:

Current game_L is lua_State 0x05D26970
lua_State 0x05D26970
lua_State 0x05D26D18
lua_State 0x05D26BE0
lua_State 0x6BA96D30

Here is a listing of the globals with each context highlighted:

lua_State 0x05D26970
0 FUN xpcall = 0x05EC8658
0 FUN ZeroDomain = 0x184480F8
0 FUN TorusDomain = 0x180D50C8
0 FUN tostring = 0x05EC8478
0 FUN gcinfo = 0x05EC8628
0 TAB Compound = 0x05D35C58
0 FUN AxisAttractor = 0x180D5128
0 FUN PreserveRelPos = 0x180D50B0
0 FUN SphereDomain = 0x18448128
0 FUN unpack = 0x05EC84C0
0 FUN dofile = 0x05EC8598
0 FUN Named = 0x180D50F8
0 FUN require = 0x05EC86A0
0 FUN getfenv = 0x05EC8520
0 STR _VERSION = Lua 5.0.2
0 FUN VectorOffsetReaction = 0x180D5068
0 FUN setmetatable = 0x05EC8508
0 FUN next = 0x05EC8550
0 FUN Turbulence = 0x180D5110
0 FUN assert = 0x05EC84D8
0 FUN Pts = 0x180D5038
0 FUN tonumber = 0x05EC8490
0 FUN newproxy = 0x05D35A28
0 FUN CollidePlane = 0x184480B0
0 FUN rawequal = 0x05EC85F8
0 FUN ImpulseSpawner = 0x180D5098
0 FUN collectgarbage = 0x05EC8640
0 FUN System = 0x18448170
0 FUN getmetatable = 0x05D40E20
0 FUN AirDrag = 0x18448038
0 FUN KillOld = 0x18448098
0 FUN VectorFactor = 0x18448050
0 FUN AnimatedColor = 0x18448080
0 FUN AnimatedScalar = 0x18448068
0 TAB _LOADED = 0x05D35C08
0 TAB _G = 0x05D35870
0 FUN Gravity = 0x184480C8
0 FUN HemisphereDomain = 0x184480E0
0 FUN ContextPts = 0x180D5020
0 TAB coroutine = 0x05D35A00
0 FUN pairs = 0x05EC8580
0 FUN ContinuousSpawner = 0x18448020
0 TAB math = 0x05D35A50
0 FUN Material = 0x184481A0
0 FUN pcall = 0x05EC85B0
0 FUN Particles = 0x18448188
0 TAB Pfx = 0x17FF8CA0
0 FUN type = 0x05EC84F0
0 FUN __pow = 0x184482C0
0 FUN rawset = 0x05EC85C8
0 FUN BoxDomain = 0x18448140
0 FUN print = 0x05EC84A8
0 FUN SpawnReaction = 0x180D5080
0 FUN rawget = 0x05EC85E0
0 FUN loadstring = 0x05EC86B8
0 FUN CompoundDomain = 0x18448110
0 FUN TrajectorySpawner = 0x180D5050
0 FUN setfenv = 0x05EC8538
0 FUN DomainBuilder = 0x18448158
0 FUN ipairs = 0x05EC8568
0 FUN error = 0x05D40E38
0 FUN loadfile = 0x05EC8610
lua_State 0x05D26D18
0 TAB render = 0x05CEA700
0 STR OBJECT_DONE_SUFFIX = _shadow
0 FUN spawn_usa = 0x05D2EF40
0 FUN tostring = 0x005A1A28
0 FUN gcinfo = 0x005A1B18
0 FUN showNextMissionObjects = 0x05D2CB70
0 FUN mapOnStartMissionSpecific = 0x05D2CBA0
0 FUN timer_01 = 0x05D2EEB0
0 FUN lose = 0x05D2CB10
0 FUN getfenv = 0x005A1980
0 FUN isValid = 0x05D2CA50
0 FUN pairs = 0x005A19E0
0 FUN assert = 0x005A1A58
0 FUN zone_centr = 0x05D2EDC0
0 FUN tonumber = 0x005A1A10
0 TAB DEMO_MISSIONS = 0x05D2B870
0 TAB console = 0x0056B668
0 FUN win = 0x05D2CAE0
0 STR SQUAD = squad
0 TAB _G = 0x0056B258
0 FUN addSquad = 0x05D2CAB0
0 FUN zone_right = 0x05D2EE20
0 TAB coroutine = 0x0056B550
0 FUN moveUnit = 0x05CE53E8
0 STR TANK = tank
0 FUN addTank = 0x05D2C780
0 FUN loadstring = 0x005A1B60
0 FUN mapOnStart = 0x05D2CB40
0 FUN getNextMission = 0x0056D028
0 FUN mapOnMouseLeftUp = 0x05D2CBD0
0 FUN zone_left = 0x05D2EDF0
0 TAB utils = 0x0056B730
0 FUN xpcall = 0x005A1AE8
0 TAB Missions = 0x05D2B898
0 FUN spawn_enemy = 0x05D2EF10
0 STR _VERSION = Lua 5.0.2
0 STR PLAYER = usa
0 FUN isFunction = 0x0056CF68
0 STR maxZoom = 400
0 FUN __pow = 0x0056DF98
0 TAB Reserves = 0x05D2BFC8
0 STR minZoom = 200
0 FUN addCar = 0x05D2C9C0
0 STR global_map = sa
0 FUN print = 0x005A19F8
0 FUN timer_02 = 0x05D2EEE0
0 FUN setmetatable = 0x00592C00
0 FUN next = 0x005A19B0
0 TAB trigger = 0x05D2A8F8
0 FUN bonus_01 = 0x05D2EE80
0 FUN onGameStarted = 0x05D2EFA0
0 STR SCRIPTS_PATH = scripts/lua/
0 TAB HiddenMissions = 0x05D2B910
0 FUN onDemoMissionStarted = 0x05D2EF70
0 FUN rawequal = 0x005A1A88
0 TAB spawns = 0x05D35820
0 FUN collectgarbage = 0x005A1B00
0 TAB game = 0x05D3B5A8
0 FUN newproxy = 0x0056B500
0 FUN unpack = 0x005A1A70
0 FUN spawnHeli = 0x05CE53B8
0 FUN spawnSquad = 0x05CE5388
0 FUN spawnCar = 0x05CE5358
0 FUN spawnUnit = 0x05CE52F8
0 FUN rawset = 0x005A1AB8
0 FUN spawnTank = 0x05CE5328
0 FUN mapOnLeaveTacticalMap = 0x05CE4C68
0 FUN mapOnOkButton = 0x05CE4C38
0 FUN addMissionReserve = 0x05D2CC00
0 FUN help = 0x0056CF98
0 TAB string = 0x0056B5A0
0 TAB math = 0x0056B618
0 FUN dofile = 0x05BDE468
0 FUN pcall = 0x005A1AD0
0 FUN bonus_02 = 0x05D2EE50
0 FUN require = 0x005A1B78
0 FUN type = 0x005A1A40
0 TAB table = 0x0056B5C8
0 FUN ipairs = 0x005A19C8
0 FUN getmetatable = 0x0056B968
0 TAB _LOADED = 0x0056B578
0 STR HELI = heli
0 FUN rawget = 0x005A1AA0
0 STR CAR = car
0 TAB cheats = 0x0056B780
0 STR spawn_name = Add_01_01
0 FUN setfenv = 0x005A1998
0 FUN addHeli = 0x05D2CA80
0 FUN addUnit = 0x05D2C4B0
0 FUN error = 0x0056F3C8
0 FUN loadfile = 0x05BDE480
lua_State 0x05D26BE0
0 TAB render = 0x05CEA700
0 STR OBJECT_DONE_SUFFIX = _shadow
0 FUN spawn_usa = 0x05D2EF40
0 FUN tostring = 0x005A1A28
0 FUN gcinfo = 0x005A1B18
0 FUN showNextMissionObjects = 0x05D2CB70
0 FUN mapOnStartMissionSpecific = 0x05D2CBA0
0 FUN timer_01 = 0x05D2EEB0
0 FUN lose = 0x05D2CB10
0 FUN getfenv = 0x005A1980
0 FUN isValid = 0x05D2CA50
0 FUN pairs = 0x005A19E0
0 FUN assert = 0x005A1A58
0 FUN zone_centr = 0x05D2EDC0
0 FUN tonumber = 0x005A1A10
0 TAB DEMO_MISSIONS = 0x05D2B870
0 TAB console = 0x0056B668
0 FUN win = 0x05D2CAE0
0 STR SQUAD = squad
0 TAB _G = 0x0056B258
0 FUN addSquad = 0x05D2CAB0
0 FUN zone_right = 0x05D2EE20
0 TAB coroutine = 0x0056B550
0 FUN moveUnit = 0x05CE53E8
0 STR TANK = tank
0 FUN addTank = 0x05D2C780
0 FUN loadstring = 0x005A1B60
0 FUN mapOnStart = 0x05D2CB40
0 FUN getNextMission = 0x0056D028
0 FUN mapOnMouseLeftUp = 0x05D2CBD0
0 FUN zone_left = 0x05D2EDF0
0 TAB utils = 0x0056B730
0 FUN xpcall = 0x005A1AE8
0 TAB Missions = 0x05D2B898
0 FUN spawn_enemy = 0x05D2EF10
0 STR _VERSION = Lua 5.0.2
0 STR PLAYER = usa
0 FUN isFunction = 0x0056CF68
0 STR maxZoom = 400
0 FUN __pow = 0x0056DF98
0 TAB Reserves = 0x05D2BFC8
0 STR minZoom = 200
0 FUN addCar = 0x05D2C9C0
0 STR global_map = sa
0 FUN print = 0x005A19F8
0 FUN timer_02 = 0x05D2EEE0
0 FUN setmetatable = 0x00592C00
0 FUN next = 0x005A19B0
0 TAB trigger = 0x05D2A8F8
0 FUN bonus_01 = 0x05D2EE80
0 FUN onGameStarted = 0x05D2EFA0
0 STR SCRIPTS_PATH = scripts/lua/
0 TAB HiddenMissions = 0x05D2B910
0 FUN onDemoMissionStarted = 0x05D2EF70
0 FUN rawequal = 0x005A1A88
0 TAB spawns = 0x05D35820
0 FUN collectgarbage = 0x005A1B00
0 TAB game = 0x05D3B5A8
0 FUN newproxy = 0x0056B500
0 FUN unpack = 0x005A1A70
0 FUN spawnHeli = 0x05CE53B8
0 FUN spawnSquad = 0x05CE5388
0 FUN spawnCar = 0x05CE5358
0 FUN spawnUnit = 0x05CE52F8
0 FUN rawset = 0x005A1AB8
0 FUN spawnTank = 0x05CE5328
0 FUN mapOnLeaveTacticalMap = 0x05CE4C68
0 FUN mapOnOkButton = 0x05CE4C38
0 FUN addMissionReserve = 0x05D2CC00
0 FUN help = 0x0056CF98
0 TAB string = 0x0056B5A0
0 TAB math = 0x0056B618
0 FUN dofile = 0x05BDE468
0 FUN pcall = 0x005A1AD0
0 FUN bonus_02 = 0x05D2EE50
0 FUN require = 0x005A1B78
0 FUN type = 0x005A1A40
0 TAB table = 0x0056B5C8
0 FUN ipairs = 0x005A19C8
0 FUN getmetatable = 0x0056B968
0 TAB _LOADED = 0x0056B578
0 STR HELI = heli
0 FUN rawget = 0x005A1AA0
0 STR CAR = car
0 TAB cheats = 0x0056B780
0 STR spawn_name = Add_01_01
0 FUN setfenv = 0x005A1998
0 FUN addHeli = 0x05D2CA80
0 FUN addUnit = 0x05D2C4B0
0 FUN error = 0x0056F3C8
0 FUN loadfile = 0x05BDE480
lua_State 0x6BA96D30
0 TAB render = 0x05CEA700
0 STR OBJECT_DONE_SUFFIX = _shadow
0 FUN spawn_usa = 0x05D2EF40
0 FUN tostring = 0x005A1A28
0 FUN gcinfo = 0x005A1B18
0 FUN showNextMissionObjects = 0x05D2CB70
0 FUN mapOnStartMissionSpecific = 0x05D2CBA0
0 FUN timer_01 = 0x05D2EEB0
0 FUN lose = 0x05D2CB10
0 FUN getfenv = 0x005A1980
0 FUN isValid = 0x05D2CA50
0 FUN pairs = 0x005A19E0
0 FUN assert = 0x005A1A58
0 FUN zone_centr = 0x05D2EDC0
0 FUN tonumber = 0x005A1A10
0 TAB DEMO_MISSIONS = 0x05D2B870
0 TAB console = 0x0056B668
0 FUN win = 0x05D2CAE0
0 STR SQUAD = squad
0 TAB _G = 0x0056B258
0 FUN addSquad = 0x05D2CAB0
0 FUN zone_right = 0x05D2EE20
0 TAB coroutine = 0x0056B550
0 FUN moveUnit = 0x05CE53E8
0 STR TANK = tank
0 FUN addTank = 0x05D2C780
0 FUN loadstring = 0x005A1B60
0 FUN mapOnStart = 0x05D2CB40
0 FUN getNextMission = 0x0056D028
0 FUN mapOnMouseLeftUp = 0x05D2CBD0
0 FUN zone_left = 0x05D2EDF0
0 TAB utils = 0x0056B730
0 FUN xpcall = 0x005A1AE8
0 TAB Missions = 0x05D2B898
0 FUN spawn_enemy = 0x05D2EF10
0 STR _VERSION = Lua 5.0.2
0 STR PLAYER = usa
0 FUN isFunction = 0x0056CF68
0 STR maxZoom = 400
0 FUN __pow = 0x0056DF98
0 TAB Reserves = 0x05D2BFC8
0 STR minZoom = 200
0 FUN addCar = 0x05D2C9C0
0 STR global_map = sa
0 FUN print = 0x005A19F8
0 FUN timer_02 = 0x05D2EEE0
0 FUN setmetatable = 0x00592C00
0 FUN next = 0x005A19B0
0 TAB trigger = 0x05D2A8F8
0 FUN bonus_01 = 0x05D2EE80
0 FUN onGameStarted = 0x05D2EFA0
0 STR SCRIPTS_PATH = scripts/lua/
0 TAB HiddenMissions = 0x05D2B910
0 FUN onDemoMissionStarted = 0x05D2EF70
0 FUN rawequal = 0x005A1A88
0 TAB spawns = 0x05D35820
0 FUN collectgarbage = 0x005A1B00
0 TAB game = 0x05D3B5A8
0 FUN newproxy = 0x0056B500
0 FUN unpack = 0x005A1A70
0 FUN spawnHeli = 0x05CE53B8
0 FUN spawnSquad = 0x05CE5388
0 FUN spawnCar = 0x05CE5358
0 FUN spawnUnit = 0x05CE52F8
0 FUN rawset = 0x005A1AB8
0 FUN spawnTank = 0x05CE5328
0 FUN mapOnLeaveTacticalMap = 0x05CE4C68
0 FUN mapOnOkButton = 0x05CE4C38
0 FUN addMissionReserve = 0x05D2CC00
0 FUN help = 0x0056CF98
0 TAB string = 0x0056B5A0
0 TAB math = 0x0056B618
0 FUN dofile = 0x05BDE468
0 FUN pcall = 0x005A1AD0
0 FUN bonus_02 = 0x05D2EE50
0 FUN require = 0x005A1B78
0 FUN type = 0x005A1A40
0 TAB table = 0x0056B5C8
0 FUN ipairs = 0x005A19C8
0 FUN getmetatable = 0x0056B968
0 TAB _LOADED = 0x0056B578
0 STR HELI = heli
0 FUN rawget = 0x005A1AA0
0 STR CAR = car
0 TAB cheats = 0x0056B780
0 STR spawn_name = Add_01_01
0 FUN setfenv = 0x005A1998
0 FUN addHeli = 0x05D2CA80
0 FUN addUnit = 0x05D2C4B0
0 FUN error = 0x0056F3C8
0 FUN loadfile = 0x05BDE480

 

Conclusion

What’s interesting is that every time I executed a command on the in-game console, lua_gettop hook would be called with different Lua contexts. However, if I set a global variable, such as “maxZoom” to a new value, that variable’s value would change in all the other contexts. In the next post in this series, I will show various ways I poked the game with the knowledge I gained.

Hooking Lua Part 1

Reproduced from https://sites.google.com/site/sbobovyc/writing/reverse-engineering/hooking-lua-part-1

Introduction

Lua is a widely used scripting language in games. When it comes to modding/hacking games, getting access to the scripting system of a game is a huge win. I decided to learn Lua and set up a few experiments in order to learn how to hook into it. The following is a write up of my adventures in Lua.

Compiling, linking, hooking

There are a few different ways to use Lua in a game. Most games are written in C++, and the Lua interpreter is called from inside native code. A game can also register functions with Lua, and after registration these native functions can be used from inside Lua scripts. My little toy application registers two native functions and executes some Lua scripts. The injectable DLL calls a Lua script that dumps the Lua global variables to a file. The globals will contain all the functions that can be called, and include non-standard Lua function that the game registered.

I compiled Lua 5.2.3 with mingw g++ 4.8.1:
$ make mingw
This generated src/liblua.a and src/lua52.dll.

My application was linked to the static Lua library. The application registered two internal functions that simply take a Lua state as a parameter and return some integer values. The application also prints out the address of the Lua state, various Lua C API functions, and the two registered functions:

This is the address of lua state: 0x009123B8
This is the address of luaL_newstate: 0x00405C40
This is the address of luaL_loadstring: 0x00405650
This is the address of luaL_loadfilex: 0x004053E0
This is the address of luaL_loadbufferx: 0x00405600
This is the address of lua_pcallk: 0x00403000
[Exe] This is a dostring print
[Lua] internal_function_1: function: 004016C0
[Lua] internal_function_2: function: 00401790
[Exe] Called internal_function_1
[Lua] got 10
[Exe] Called internal_function_2

 
After the application is started, the DLL is injected and its DllMain function is called. The DLL uses the DllMain to start a new thread and hook the Lua state that is created in the main application. Most of the Lua C API function require a pointer to a Lua state, so the DLL needs to know its address in memory. No special memory allocator was used, so Lua uses realloc is used to allocate the state on the heap and the address of the state is not known ahead of time. To make things easier, I had the application write the address to a file and then the DLL reads the address from the file. Since the DLL is statically linked to Lua, it will use it’s own Lua functions instead of the application’s functions. If the application and the DLL were dynamically linked to Lua, when the DLL tried to access the Lua state it would cause a “multiple Lua VMs detected” error. The DLL executes a Lua script that dumps _G, the global Lua variables to a log file named state_dump.txt. This variable is a dictionary, and it contains all the functions that could be called from Lua. The addresses of the two internal functions can be seen in the dump:

internal_function_2 = function: 00401790
internal_function_1 = function: 004016C0

A game would contain many registered functions, and their names and addresses could be used to reverse engineer some interesting parts of the game.

Debugging symbols where included which makes finding Lua function in the application easy.
This is the location of luaL_newstate. Notice that the function luaL_checkversion_ is right below it:

00405C40 >/$ 53             PUSH EBX
00405C41  |. 83EC 18        SUB ESP,18
00405C44  |. C74424 04 0000>MOV DWORD PTR SS:[ESP+4],0
00405C4C  |. C70424 D042400>MOV DWORD PTR SS:[ESP],lua_simp.004042D0
00405C53  |. E8 C8DDFFFF    CALL lua_simp.lua_newstate
00405C58  |. 85C0           TEST EAX,EAX
00405C5A  |. 89C3           MOV EBX,EAX
00405C5C  |. 74 10          JE SHORT lua_simp.00405C6E
00405C5E  |. C74424 04 8042>MOV DWORD PTR SS:[ESP+4],lua_simp.00404280
00405C66  |. 890424         MOV DWORD PTR SS:[ESP],EAX
00405C69  |. E8 52BFFFFF    CALL lua_simp.lua_atpanic
00405C6E  |> 83C4 18        ADD ESP,18
00405C71  |. 89D8           MOV EAX,EBX
00405C73  |. 5B             POP EBX
00405C74  \. C3             RETN
00405C75     8D7426 00      LEA ESI,DWORD PTR DS:[ESI]
00405C79     8D             DB 8D
00405C7A     BC             DB BC
00405C7B     27             DB 27                                                  ;  CHAR '''
00405C7C     00             DB 00
00405C7D     00             DB 00
00405C7E     00             DB 00
00405C7F     00             DB 00
00405C80 > $ 56             PUSH ESI
00405C81   . 53             PUSH EBX
00405C82   . 83EC 24        SUB ESP,24
00405C85   . 8B5C24 30      MOV EBX,DWORD PTR SS:[ESP+30]
00405C89   . DD4424 34      FLD QWORD PTR SS:[ESP+34]
00405C8D   . DD5C24 18      FSTP QWORD PTR SS:[ESP+18]
00405C91   . 891C24         MOV DWORD PTR SS:[ESP],EBX
00405C94   . E8 47BFFFFF    CALL lua_simp.lua_version
00405C99   . C70424 0000000>MOV DWORD PTR SS:[ESP],0
00405CA0   . 89C6           MOV ESI,EAX
00405CA2   . E8 39BFFFFF    CALL lua_simp.lua_version
00405CA7   . 39F0           CMP EAX,ESI
00405CA9   . 74 75          JE SHORT lua_simp.00405D20
00405CAB   . C74424 04 3636>MOV DWORD PTR SS:[ESP+4],lua_simp.00423636             ;  ASCII "multiple Lua VMs detected"
00405CB3   . 891C24         MOV DWORD PTR SS:[ESP],EBX
00405CB6   . E8 75EAFFFF    CALL lua_simp.luaL_error

This is the source code of the two functions:

LUALIB_API lua_State *luaL_newstate (void) {
  lua_State *L = lua_newstate(l_alloc, NULL);
  if (L) lua_atpanic(L, &panic);
  return L;
}


LUALIB_API void luaL_checkversion_ (lua_State *L, lua_Number ver) {
  const lua_Number *v = lua_version(L);
  if (v != lua_version(NULL))
    luaL_error(L, "multiple Lua VMs detected");
  else if (*v != ver)
    luaL_error(L, "version mismatch: app. needs %f, Lua core provides %f",
                  ver, *v);
....

Depending on how Lua is compiled into a game, this could be used to find and hook the luaL_newstate function and get the pointer to any Lua states that are created.

Source for these experiments can be found here https://github.com/sbobovyc/WinHookInject/tree/master/lua_experiments

Notes

http://stackoverflow.com/questions/11324117/how-do-modern-vms-handle-memory-allocation