From the implemented fuzzer, thousands of crash files were generated. Since we are just a small team and we had limited time for this project, we needed some strategy to classify our crash files. Also, as crash files were .lua script files, we spent much time on analyzing root cause of crash files. We believe this post would be helpful to those who are trying to analyze lua script that causes crash on Lua interprerter.
1. Classifying crash files
1) Comparing hash value of call stack’s PC
Under the same environment, we made an assumption that if hash values of call stack’s PC(from call stack #0 to call stack #4) of two crash files are same, they are overlapped. We made script files to automate comparing process. You can refer the script files from this link. Using this method the number of crash files was reduced from thousands to hundreds.
2) Checking crash type
Hundreds of crash files are still a long way to go. There were various kinds of crash files. Crash files caused use-after-free, heap overflow, stack overflow, segmentation violation, etc. Not qqqqqqqqqqall crash files are related to worthwhile vulnerability. Our mentor recommended us to give precedence to crash type. We first analyzed use-after-free, segmentation violation, then heap overflow, and so on. Recently, we could find an exploitable vulnerability from use-after-free crash file. It would be helpful to first analyze suspicious crash files before trivial ones.
2. Tips for analyzing the crash
makefile
-
AddressSanitizer If you want to use AddressSanitizer, you need to add
-fsanitize=address
option to the makefile. -
GDB debugging with source code If you want to debug with source code, you need to add
-g -O0
options to the makefile.
-
A makefile with nothing applied
# skipped # enable Linux goodies MYCFLAGS= $(LOCAL) -std=c99 -DLUA_USE_LINUX -DLUA_USE_READLINE MYLDFLAGS= $(LOCAL) -Wl,-E MYLIBS= -ldl -lreadline CC= gcc CFLAGS= -Wall -O2 $(MYCFLAGS) -fno-stack-protector -fno-common -march=native AR= ar rc RANLIB= ranlib RM= rm -f # skipped
-
ASAN, debug wth source code applied makefile
# skipped # enable Linux goodies MYCFLAGS= $(LOCAL) -g -O0 -std=c99 -DLUA_USE_LINUX -DLUA_USE_READLINE -fsanitize=address #-DLUAI_ASSERT -DLUA_USE_APICHECK MYLDFLAGS= $(LOCAL) -Wl,-E MYLIBS= -ldl -lreadline -fsanitize=address CC= gcc CFLAGS= -Wall $(MYCFLAGS) -fno-stack-protector -fno-common -march=native AR= ar rc RANLIB= ranlib RM= rm -f # skipped
Crash Minimization
It is efficient to reduce the code of the crash to analyze the root cause of the crash. Below is an example of actually reducing the crashes from the project’s fuzzer.
- git commit hash : ad3942adba574c9d008c99ce2785a5af19d146bf
example original crash
local function c(a,b) local label = string.gsub("0123456789", 40) end
local function v(f, ...) string.packsize"XXxX" end
setmetatable({}, {__gc = function () string.packsize(type"", "^([^\n]+)\n") end})
local b,c = next(string)
local function v(f, ...) local function c(x)
os.exit(err, "[^\n]", "")
end
local function v(x)
os.exit(err, "[^\n]", "")
end
local a <close> = setmetatable({}, {__close = c})
return v(function() return x end) end
local a <close> = setmetatable({}, {__close = c})
return v(function() return x end)
example minimal crash
local function v(a, b, c, ...)
return os.exit(0, true)
end
local function a()
return h()
end
local e <close> = setmetatable({}, {__close = string.rep})
v()
ByteCode
Due to the characteristics of the lua interpreter, the parsed code is converted into bytecode and executed. Therefore, in order to analyze the root cause of a minimized crash, it is necessary to know which bytecode the crash code is converted to. So, we analyzed the crash by referring to the luac.nl web page that converts lua code into bytecode.
The converted bytecode is operated in the luaV_execute
function inside Lua. After checking which bytecode among the bytecodes generated by the crash code causes the crash to occur, we focused on that part to find out the root cause.