1002 lines
29 KiB
C
1002 lines
29 KiB
C
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// SPDX-FileCopyrightText: 2017 pancake <pancake@nopcode.org>
|
|
// SPDX-FileCopyrightText: 2021 Heersin <teablearcher@gmail.com>
|
|
// SPDX-FileCopyrightText: 2025-2026 Sergey Sharshunov <s.sharshunov@gmail.com>
|
|
|
|
#include "librz/bin/format/luac/luac_common.h"
|
|
|
|
#ifdef RZ_DEBUG
|
|
#define CHECK_SIZE \
|
|
if (offset >= bf->size) { \
|
|
RZ_LOG_ERROR("Truncated Header (line: %d)\n", __LINE__); \
|
|
return 0; \
|
|
}
|
|
#else
|
|
#define CHECK_SIZE rz_return_val_if_fail((offset < bf->size), 0)
|
|
#endif
|
|
|
|
static ut8 read8(RzBuffer *buffer, ut64 *offset, ut8 *receiver) {
|
|
ut8 tmp;
|
|
if (!rz_buf_read_ble8_offset(buffer, offset, &tmp, false)) {
|
|
return 0;
|
|
}
|
|
*receiver = tmp;
|
|
return tmp;
|
|
}
|
|
|
|
#define READ8_CHECK_SIZE(buffer, offset, receiver) \
|
|
{ \
|
|
read8(buffer, &offset, &receiver); \
|
|
CHECK_SIZE; \
|
|
}
|
|
|
|
static int read8_check_val(RzBuffer *buffer, ut64 *offset, const ut16 val, const char *err) {
|
|
ut8 tmp = 0;
|
|
read8(buffer, offset, &tmp);
|
|
if (tmp != val) {
|
|
RZ_LOG_ERROR("%s\n", err);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#define READ8_CHECK_VAL(buffer, offset, val, err) \
|
|
read8_check_val(buffer, offset, val, err); \
|
|
CHECK_SIZE;
|
|
|
|
static ut64 lua_load_integer(RzBuffer *buffer, ut64 offset) {
|
|
ut64 x = 0;
|
|
rz_buf_read_le64_at(buffer, offset, &x);
|
|
return x;
|
|
}
|
|
|
|
static double lua_load_number(RzBuffer *buffer, ut64 offset) {
|
|
double x = 0;
|
|
rz_buf_read_le64_at(buffer, offset, (ut64 *)&x);
|
|
return x;
|
|
}
|
|
|
|
static ut32 lua_load_int(RzBuffer *buffer, ut64 offset) {
|
|
ut32 x = 0;
|
|
rz_buf_read_le32_at(buffer, offset, &x);
|
|
return x;
|
|
}
|
|
|
|
// return an offset to skip string, return 1 if no string (0x80)
|
|
static ut64 lua_parse_szint(RzBuffer *buffer, st32 *size, ut64 offset, ut64 data_size, ut8 minor) {
|
|
st32 result = 0;
|
|
st32 i = 0;
|
|
ut8 b;
|
|
st32 shift = 0;
|
|
while (offset + i < data_size) {
|
|
if (!rz_buf_read8_at(buffer, offset + i, &b)) {
|
|
return 0;
|
|
}
|
|
i++;
|
|
if (minor == 4) {
|
|
result = (result << 7) | (b & 0x7f);
|
|
if (b & 0x80) {
|
|
*size = result;
|
|
return i;
|
|
}
|
|
} else if (minor == 5) {
|
|
result |= (b & 0x7f) << shift;
|
|
if (!(b & 0x80)) {
|
|
*size = result;
|
|
return i;
|
|
}
|
|
shift += 7;
|
|
if (shift >= 64)
|
|
break;
|
|
}
|
|
}
|
|
return i;
|
|
}
|
|
|
|
static ut64 lua_parse_number(RzBuffer *buffer, st32 *size, ut64 offset, ut64 data_size, ut8 minor) {
|
|
if (minor >= 4) {
|
|
return lua_parse_szint(buffer, size, offset, data_size, minor);
|
|
}
|
|
if (minor <= 3) {
|
|
const size_t size_offset = sizeof(LUA_INT);
|
|
if (size_offset + offset > data_size) {
|
|
RZ_LOG_ERROR("file truncated, offset: %" PFMT64u ", data size: %" PFMT64u "\n", offset, data_size);
|
|
return 0;
|
|
}
|
|
*size = (st32)lua_load_int(buffer, offset);
|
|
return size_offset;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ut64 lua_parse_line_defined(LuaProto *proto, RzBuffer *buffer, ut64 offset, ut64 data_size, ut8 minor) {
|
|
ut64 size_offset = 0;
|
|
int line_defined = 0;
|
|
int last_line_defined = 0;
|
|
|
|
size_offset = lua_parse_number(buffer, &line_defined, offset, data_size, minor);
|
|
lua_check_error_offset(size_offset);
|
|
|
|
/* Set Proto Member */
|
|
proto->line_defined = (ut32)line_defined;
|
|
|
|
if (minor == 0) {
|
|
return size_offset;
|
|
}
|
|
|
|
const ut64 delta_offset = lua_parse_number(buffer, &last_line_defined, offset + size_offset, data_size, minor);
|
|
lua_check_error_offset(delta_offset);
|
|
size_offset += delta_offset;
|
|
|
|
proto->lastline_defined = (ut32)last_line_defined;
|
|
return size_offset;
|
|
}
|
|
|
|
static ut64 lua_parse_string(RzBuffer *buffer, ut8 **dest, int *str_len, ut64 offset, ut64 data_size, ut8 minor, ut8 num_size) {
|
|
ut64 size_offset = 0;
|
|
int ret_buf_size = 0;
|
|
int string_len = 0;
|
|
ut8 *ret = 0x00;
|
|
|
|
const ut64 base_offset = offset;
|
|
|
|
if (minor <= 2) {
|
|
ut16 tmp16 = 0;
|
|
if (!rz_buf_read_le16_at(buffer, offset, &tmp16)) {
|
|
return 0;
|
|
}
|
|
ret_buf_size = (int)tmp16;
|
|
size_offset = num_size;
|
|
offset += size_offset;
|
|
} else if (minor == 3) {
|
|
ut8 tmp = 0;
|
|
if (!rz_buf_read8_at(buffer, offset, &tmp)) {
|
|
return 0;
|
|
}
|
|
ret_buf_size = (int)tmp;
|
|
size_offset = 1;
|
|
if (ret_buf_size == 0xFF) {
|
|
offset += size_offset;
|
|
ut16 tmp16 = 0;
|
|
if (!rz_buf_read_le16_at(buffer, offset, &tmp16)) {
|
|
return 0;
|
|
}
|
|
ret_buf_size = (int)tmp16;
|
|
size_offset = num_size;
|
|
}
|
|
offset += size_offset;
|
|
} else if (minor >= 4) {
|
|
size_offset = lua_parse_szint(buffer, &ret_buf_size, offset, data_size, minor);
|
|
lua_check_error_offset(size_offset);
|
|
offset += size_offset;
|
|
}
|
|
|
|
/* no string */
|
|
if (ret_buf_size == 0) {
|
|
ret = NULL;
|
|
string_len = 0;
|
|
} else {
|
|
/* skip size byte */
|
|
string_len = ret_buf_size - 1;
|
|
ret = RZ_NEWS(ut8, ret_buf_size);
|
|
if (ret == NULL) {
|
|
string_len = 0;
|
|
} else {
|
|
rz_buf_read_at(buffer, offset, ret, string_len);
|
|
ret[string_len] = 0x00;
|
|
}
|
|
offset += string_len;
|
|
}
|
|
|
|
/* set to outside vars */
|
|
if (dest && str_len) {
|
|
*dest = ret;
|
|
*str_len = string_len;
|
|
} else {
|
|
RZ_LOG_ERROR("Cannot store string\n");
|
|
}
|
|
|
|
if (((minor == 0) && (ret_buf_size > 0)) || (minor == 2) || (minor == 5))
|
|
offset++;
|
|
return offset - base_offset;
|
|
}
|
|
|
|
static ut64 lua_parse_name(LuaProto *proto, RzBuffer *buffer, ut64 offset, ut64 data_size, ut8 minor) {
|
|
return lua_parse_string(buffer, &proto->proto_name, &proto->name_size, offset, data_size, minor, proto->num_size);
|
|
}
|
|
|
|
static ut64 lua_parse_code(LuaProto *proto, RzBuffer *buffer, ut64 offset, ut64 data_size, ut8 minor) {
|
|
ut64 size_offset = 0;
|
|
int code_size = 0; ///< instructions
|
|
|
|
size_offset = lua_parse_number(buffer, &code_size, offset, data_size, minor);
|
|
lua_check_error_offset(size_offset);
|
|
|
|
if (minor == 5) {
|
|
const ut64 aligned_size = (offset + size_offset + 3) & ~3;
|
|
size_offset = aligned_size - offset;
|
|
}
|
|
const ut8 instruction_size = (minor == 0) ? 8 : 4;
|
|
const ut64 total_size = code_size * instruction_size + size_offset;
|
|
if (total_size + offset > data_size) {
|
|
RZ_LOG_ERROR("Truncated Code at [0x%" PFMT64x "]\n", offset);
|
|
return 0;
|
|
}
|
|
|
|
/* Set Proto Member */
|
|
proto->code_size = code_size * instruction_size;
|
|
proto->code_skipped = size_offset;
|
|
return total_size;
|
|
}
|
|
|
|
static ut64 lua_parse_const_entry(const LuaProto *proto, RzBuffer *buffer, int index, ut64 offset, ut64 prev_offset, ut64 data_size, ut8 minor) {
|
|
ut8 *recv_data = NULL;
|
|
int data_len = 0;
|
|
|
|
LuaConstEntry *current_entry = lua_new_const_entry();
|
|
const ut64 k_vaddr_base = K_VADDRESS_BASE(proto->index);
|
|
current_entry->offset = offset;
|
|
current_entry->prev_offset = prev_offset;
|
|
current_entry->voffset = k_vaddr_base + prev_offset;
|
|
current_entry->string_start = current_entry->voffset + 2;
|
|
const ut64 base_offset = offset;
|
|
ut64 delta_offset = 0;
|
|
|
|
/* read TAG byte */
|
|
if (offset + 1 > data_size) {
|
|
return 0;
|
|
}
|
|
if (!rz_buf_read8_at(buffer, offset, ¤t_entry->tag)) {
|
|
return 0;
|
|
}
|
|
offset += 1;
|
|
|
|
ut8 tmp;
|
|
/* read data */
|
|
switch (current_entry->tag) {
|
|
case LUA_VNUMINT:
|
|
data_len = sizeof(LUA_NUMBER);
|
|
recv_data = RZ_NEWS(ut8, data_len);
|
|
rz_buf_read_le64_at(buffer, offset, (ut64 *)recv_data);
|
|
if (offset + data_len > data_size) {
|
|
RZ_FREE(recv_data);
|
|
return 0;
|
|
}
|
|
delta_offset = data_len;
|
|
if (minor == 3) {
|
|
current_entry->tag = LUA_VNUMFLT; // keep the same with 5.4 tag
|
|
break;
|
|
}
|
|
if ((minor == 0) || (minor == 1) || (minor == 2)) {
|
|
double intPart1;
|
|
const double r = *(double *)recv_data;
|
|
const double fractionalPart = modf(r, &intPart1);
|
|
if (fractionalPart == 0.0) {
|
|
current_entry->tag = LUA_VNUMINT; // keep the same with 5.4 tag
|
|
*(ut64 *)recv_data = (ut64)intPart1;
|
|
} else {
|
|
current_entry->tag = LUA_VNUMFLT; // keep the same with 5.4 tag
|
|
}
|
|
}
|
|
break;
|
|
case LUA_VNUMFLT:
|
|
#if 0
|
|
// TODO: need check on other platforms
|
|
if (minor >= 4) {
|
|
data_len = 8;
|
|
} else if (minor == 2) {
|
|
data_len = sizeof(LUA_NUMBER);
|
|
} else if (minor == 3) {
|
|
data_len = sizeof(LUA_INTEGER);
|
|
}
|
|
#else
|
|
data_len = sizeof(LUA_NUMBER);
|
|
if (minor == 3) {
|
|
data_len = sizeof(LUA_INTEGER);
|
|
}
|
|
#endif
|
|
recv_data = RZ_NEWS(ut8, data_len);
|
|
rz_buf_read_le64_at(buffer, offset, (ut64 *)recv_data);
|
|
if (offset + data_len > data_size) {
|
|
RZ_FREE(recv_data);
|
|
return 0;
|
|
}
|
|
delta_offset = data_len;
|
|
current_entry->tag = LUA_VNUMINT; // keep the same with 5.4 tag
|
|
break;
|
|
case LUA_VSHRSTR:
|
|
case LUA_VLNGSTR:
|
|
delta_offset = lua_parse_string(buffer, &recv_data, &data_len, offset, data_size, minor, proto->num_size);
|
|
lua_check_error_offset(delta_offset);
|
|
if (minor == 1)
|
|
delta_offset++;
|
|
if (!recv_data) {
|
|
if (!rz_buf_read8_at(buffer, offset + 1, &tmp)) {
|
|
return 0;
|
|
}
|
|
tmp -= 1;
|
|
data_len = 1;
|
|
recv_data = RZ_NEWS(ut8, data_len);
|
|
rz_write_ble8(recv_data, tmp);
|
|
current_entry->tag = LUA_VSTRING_IDX;
|
|
}
|
|
break;
|
|
case LUA_VFALSE:
|
|
case LUA_VTRUE:
|
|
recv_data = RZ_NEWS(ut8, 1);
|
|
data_len = 1;
|
|
delta_offset = 0;
|
|
if (minor == 4 || minor == 5) {
|
|
*recv_data = current_entry->tag == LUA_VTRUE ? 1 : 0;
|
|
} else {
|
|
if (!rz_buf_read8_at(buffer, offset, (ut8 *)recv_data)) {
|
|
return 0;
|
|
}
|
|
delta_offset++;
|
|
}
|
|
break;
|
|
case LUA_OPENWRT_INT32:
|
|
data_len = 4;
|
|
recv_data = RZ_NEWS(ut8, data_len);
|
|
rz_buf_read_le16_at(buffer, offset, (ut16 *)recv_data);
|
|
if (offset + data_len > data_size) {
|
|
RZ_FREE(recv_data);
|
|
return 0;
|
|
}
|
|
delta_offset += data_len;
|
|
break;
|
|
case LUA_TNIL:
|
|
default:
|
|
recv_data = NULL;
|
|
data_len = 0;
|
|
delta_offset = 0;
|
|
break;
|
|
}
|
|
offset += delta_offset;
|
|
|
|
current_entry->data = recv_data;
|
|
current_entry->data_len = data_len;
|
|
|
|
/* add to list */
|
|
rz_pvector_push(proto->const_entries, current_entry);
|
|
return offset - base_offset;
|
|
}
|
|
|
|
static ut64 lua_parse_consts(LuaProto *proto, RzBuffer *buffer, ut64 offset, ut64 data_size, ut8 minor) {
|
|
int consts_cnt = 0;
|
|
ut64 delta_offset = 0;
|
|
|
|
const ut64 base_offset = offset;
|
|
|
|
/* parse number of constants */
|
|
delta_offset = lua_parse_number(buffer, &consts_cnt, offset, data_size, minor);
|
|
lua_check_error_offset(delta_offset);
|
|
|
|
offset += delta_offset;
|
|
ut64 prev_offset = 0;
|
|
for (int i = 0; i < consts_cnt; ++i) {
|
|
delta_offset = lua_parse_const_entry(proto, buffer, i, offset, prev_offset, data_size, minor);
|
|
lua_check_error_offset(delta_offset);
|
|
offset += delta_offset;
|
|
prev_offset += delta_offset;
|
|
}
|
|
|
|
proto->const_size = offset - (base_offset + 1);
|
|
return offset - base_offset;
|
|
}
|
|
|
|
static ut64 lua_parse_upvalue_entry(const LuaProto *proto, RzBuffer *buffer, ut64 offset, ut64 data_size, ut8 minor) {
|
|
const ut64 base_offset = offset;
|
|
LuaUpvalueEntry *current_entry = lua_new_upvalue_entry();
|
|
current_entry->offset = base_offset;
|
|
current_entry->voffset = proto->index * 0x1000 + UPVALUE_OFFSET;
|
|
|
|
// no kind in lua 5.3
|
|
const int need_kind = (minor <= 3) ? 2 : 3;
|
|
|
|
if (offset + need_kind > data_size) {
|
|
return 0;
|
|
}
|
|
|
|
/* read instack/idx attr */
|
|
if (!rz_buf_read8_at(buffer, offset + 0, ¤t_entry->instack)) {
|
|
return 0;
|
|
}
|
|
if (!rz_buf_read8_at(buffer, offset + 1, ¤t_entry->idx)) {
|
|
return 0;
|
|
}
|
|
current_entry->kind = 0;
|
|
if (minor <= 3) {
|
|
if (!rz_buf_read8_at(buffer, offset + 2, ¤t_entry->kind)) {
|
|
return 0;
|
|
}
|
|
}
|
|
offset += need_kind;
|
|
|
|
/* add to list */
|
|
rz_pvector_push(proto->upvalue_entries, current_entry);
|
|
return offset - base_offset;
|
|
}
|
|
|
|
static ut64 lua_parse_upvalues(LuaProto *proto, RzBuffer *buffer, ut64 offset, ut64 data_size, ut8 minor) { // ?
|
|
int upvalues_cnt = 0;
|
|
ut64 delta_offset = 0;
|
|
const ut64 base_offset = offset;
|
|
|
|
/* parse number of upvalues */
|
|
delta_offset = lua_parse_number(buffer, &upvalues_cnt, offset, data_size, minor);
|
|
lua_check_error_offset(delta_offset);
|
|
|
|
offset += delta_offset;
|
|
for (int i = 0; i < upvalues_cnt; ++i) {
|
|
delta_offset = lua_parse_upvalue_entry(proto, buffer, offset, data_size, minor);
|
|
lua_check_error_offset(delta_offset);
|
|
offset += delta_offset;
|
|
}
|
|
proto->upvalue_size = offset - base_offset + 1;
|
|
|
|
if (minor == 2)
|
|
proto->upvalue_size++; // TODO: check this
|
|
|
|
return offset - base_offset;
|
|
}
|
|
|
|
static ut64 lua_parse_debug(LuaProto *proto, RzBuffer *buffer, ut64 offset, ut64 data_size, ut8 minor) {
|
|
int entries_cnt = 0;
|
|
ut64 delta_offset = 0;
|
|
ut64 base_offset = offset;
|
|
|
|
/* parse line info */
|
|
delta_offset = lua_parse_number(buffer, &entries_cnt, offset, data_size, minor);
|
|
lua_check_error_offset(delta_offset);
|
|
|
|
offset += delta_offset;
|
|
ut32 line_num = proto->line_defined;
|
|
for (int i = 0; i < entries_cnt; ++i) {
|
|
LuaLineinfoEntry *info_entry = lua_new_lineinfo_entry();
|
|
if (minor > 3) {
|
|
ut64 ad = PROTO_VADDRESS(proto->index);
|
|
// READ8(buffer, offset, info_entry->info_data);
|
|
read8(buffer, &offset, &info_entry->info_data);
|
|
info_entry->info_data = line_num += DEBUG_LINE_OFFSET((st32)info_entry->info_data);
|
|
info_entry->offset = ad += i * 4;
|
|
} else {
|
|
info_entry->offset = offset;
|
|
info_entry->info_data = lua_load_int(buffer, offset);
|
|
offset += sizeof(LUA_INT);
|
|
}
|
|
rz_list_append(proto->line_info_entries, info_entry);
|
|
}
|
|
if (minor >= 4) {
|
|
/* parse absline info */
|
|
delta_offset = lua_parse_szint(buffer, &entries_cnt, offset, data_size, minor);
|
|
lua_check_error_offset(delta_offset);
|
|
RZ_LOG_DEBUG("abs_info_entry entries_cnt: %d (offset: 0x%" PFMT64x ")\n", entries_cnt, offset);
|
|
offset += delta_offset;
|
|
for (int i = 0; i < entries_cnt; ++i) {
|
|
LuaAbsLineinfoEntry *abs_info_entry = lua_new_abs_lineinfo_entry();
|
|
abs_info_entry->offset = offset;
|
|
|
|
delta_offset = lua_parse_szint(buffer, &abs_info_entry->pc, offset, data_size, minor);
|
|
lua_check_error_offset(delta_offset);
|
|
offset += delta_offset;
|
|
|
|
delta_offset = lua_parse_szint(buffer, &abs_info_entry->line, offset, data_size, minor);
|
|
lua_check_error_offset(delta_offset);
|
|
offset += delta_offset;
|
|
|
|
rz_list_append(proto->abs_line_info_entries, abs_info_entry);
|
|
}
|
|
}
|
|
|
|
/* parse local vars */
|
|
delta_offset = lua_parse_number(buffer, &entries_cnt, offset, data_size, minor);
|
|
lua_check_error_offset(delta_offset);
|
|
|
|
offset += delta_offset;
|
|
proto->local_var_offset = offset;
|
|
proto->local_var_size = 0;
|
|
ut64 prev_offset = 0;
|
|
for (int i = 0; i < entries_cnt; ++i) {
|
|
LuaLocalVarEntry *var_entry = lua_new_local_var_entry();
|
|
var_entry->offset = offset;
|
|
|
|
const ut64 k_vaddr_base = VAR_VADDRESS_BASE(proto->index);
|
|
var_entry->voffset = k_vaddr_base + prev_offset;
|
|
/* string */
|
|
delta_offset = lua_parse_string(
|
|
buffer,
|
|
&var_entry->varname, &var_entry->varname_len,
|
|
offset, data_size, minor, proto->num_size);
|
|
lua_check_error_offset(delta_offset);
|
|
if (minor == 1)
|
|
delta_offset++;
|
|
offset += delta_offset;
|
|
|
|
/* start pc -- int */
|
|
delta_offset = lua_parse_number(buffer, &var_entry->start_pc, offset, data_size, minor);
|
|
lua_check_error_offset(delta_offset);
|
|
offset += delta_offset;
|
|
|
|
/* end pc -- int */
|
|
delta_offset = lua_parse_number(buffer, &var_entry->end_pc, offset, data_size, minor);
|
|
lua_check_error_offset(delta_offset);
|
|
offset += delta_offset;
|
|
|
|
prev_offset += (offset - var_entry->offset);
|
|
var_entry->var_size = (offset - var_entry->offset);
|
|
proto->local_var_size += var_entry->var_size;
|
|
rz_pvector_push(proto->local_var_info_entries, var_entry);
|
|
}
|
|
|
|
/* parse upvalue */
|
|
delta_offset = lua_parse_number(buffer, &entries_cnt, offset, data_size, minor);
|
|
lua_check_error_offset(delta_offset);
|
|
offset += delta_offset;
|
|
for (int i = 0; i < entries_cnt; ++i) {
|
|
LuaUpvalueEntry *up = rz_pvector_at(proto->upvalue_entries, i);
|
|
if (!up) {
|
|
RZ_LOG_ERROR("invalid upvalue");
|
|
continue;
|
|
}
|
|
up->name_offset = offset;
|
|
|
|
delta_offset = lua_parse_string(
|
|
buffer,
|
|
&up->upvalue_name,
|
|
&up->name_len,
|
|
offset, data_size, minor, proto->num_size);
|
|
lua_check_error_offset(delta_offset);
|
|
offset += delta_offset;
|
|
}
|
|
|
|
proto->debug_size = offset - base_offset + 1;
|
|
return offset - base_offset;
|
|
}
|
|
|
|
static ut64 lua_parse_protos(LuaProto *proto, RzBuffer *buffer, LuaHeaderInfo *header, ut64 offset, ut64 data_size) {
|
|
rz_return_val_if_fail(proto, 0);
|
|
int proto_cnt = 0;
|
|
ut64 delta_offset = 0;
|
|
const ut64 base_offset = offset; // store origin offset
|
|
const ut8 minor = header->minor;
|
|
|
|
delta_offset = lua_parse_number(buffer, &proto_cnt, offset, data_size, minor);
|
|
lua_check_error_offset(delta_offset);
|
|
offset += delta_offset;
|
|
for (int i = 0; i < proto_cnt; ++i) {
|
|
LuaProto *current_proto = lua_parse_body(buffer, header, offset, data_size);
|
|
lua_return_if_null(current_proto);
|
|
rz_list_append(proto->proto_entries, current_proto);
|
|
offset += current_proto->size - 1; // update offset
|
|
}
|
|
// return the delta between offset and base_offset
|
|
return offset - base_offset;
|
|
}
|
|
|
|
LuaProto *lua_parse_body(RzBuffer *buffer, LuaHeaderInfo *header, ut64 base_offset, ut64 data_size) {
|
|
LuaProto *ret_proto = lua_new_proto_entry(); /* constructed proto for return */
|
|
rz_return_val_if_fail(ret_proto, NULL);
|
|
ret_proto->index = header->proto_count;
|
|
header->proto_count++;
|
|
ret_proto->num_size = header->is_openwrt ? 4 : sizeof(LUA_NUMBER);
|
|
|
|
const ut8 minor = header->minor;
|
|
ut64 delta_offset = 0;
|
|
|
|
// start parsing
|
|
ut64 offset = base_offset; /* record offset */
|
|
|
|
/* record offset of main proto */
|
|
ret_proto->offset = offset;
|
|
|
|
if ((minor == 0) || (minor == 1) || (minor == 3) || (minor == 4)) {
|
|
/* parse proto name of main proto */
|
|
delta_offset = lua_parse_name(ret_proto, buffer, offset, data_size, minor);
|
|
if (minor == 1) {
|
|
if (delta_offset > sizeof(LUA_NUMBER)) {
|
|
delta_offset += 2;
|
|
}
|
|
}
|
|
lua_check_error_offset_proto(delta_offset, ret_proto);
|
|
offset += delta_offset;
|
|
}
|
|
|
|
/* parse line defined info */
|
|
delta_offset = lua_parse_line_defined(ret_proto, buffer, offset, data_size, minor);
|
|
lua_check_error_offset_proto(delta_offset, ret_proto);
|
|
offset += delta_offset;
|
|
|
|
if ((minor == 1) && (ret_proto->line_defined > 0)) {
|
|
offset++;
|
|
}
|
|
|
|
if (minor == 0) {
|
|
int size_upvalues = 0;
|
|
ut8 tmp;
|
|
if (!rz_buf_read8_at(buffer, offset, &tmp)) {
|
|
return 0;
|
|
}
|
|
offset++;
|
|
size_upvalues = tmp;
|
|
(void)size_upvalues;
|
|
}
|
|
|
|
/* parse num params max_stack_size */
|
|
if (offset + 3 > data_size) {
|
|
lua_free_proto_entry(ret_proto);
|
|
return NULL;
|
|
}
|
|
|
|
if (!rz_buf_read8_at(buffer, offset + 0, &ret_proto->num_params) ||
|
|
!rz_buf_read8_at(buffer, offset + 1, &ret_proto->is_vararg) ||
|
|
!rz_buf_read8_at(buffer, offset + 2, &ret_proto->max_stack_size)) {
|
|
lua_free_proto_entry(ret_proto);
|
|
return NULL;
|
|
}
|
|
offset += 3;
|
|
|
|
if (minor == 0) {
|
|
/* parse debug */
|
|
ret_proto->debug_offset = offset;
|
|
delta_offset = lua_parse_debug(ret_proto, buffer, offset, data_size, minor);
|
|
lua_check_error_offset_proto(delta_offset, ret_proto);
|
|
offset += delta_offset;
|
|
|
|
/* parse constants */
|
|
ret_proto->const_offset = offset;
|
|
delta_offset = lua_parse_consts(ret_proto, buffer, offset, data_size, minor);
|
|
lua_check_error_offset_proto(delta_offset, ret_proto);
|
|
offset += delta_offset;
|
|
|
|
/* parse inner protos */
|
|
ret_proto->inner_proto_offset = offset;
|
|
delta_offset = lua_parse_protos(ret_proto, buffer, header, offset, data_size);
|
|
lua_check_error_offset_proto(delta_offset, ret_proto);
|
|
offset += delta_offset;
|
|
|
|
/* specially handle recursive protos size */
|
|
ret_proto->inner_proto_size = offset - ret_proto->inner_proto_offset;
|
|
}
|
|
|
|
/* parse code */
|
|
ret_proto->code_offset = offset;
|
|
delta_offset = lua_parse_code(ret_proto, buffer, offset, data_size, minor);
|
|
lua_check_error_offset_proto(delta_offset, ret_proto);
|
|
offset += delta_offset;
|
|
|
|
if (minor == 0) {
|
|
ret_proto->size = offset - base_offset + 1;
|
|
return ret_proto;
|
|
}
|
|
|
|
/* parse constants */
|
|
ret_proto->const_offset = offset + 1;
|
|
delta_offset = lua_parse_consts(ret_proto, buffer, offset, data_size, minor);
|
|
lua_check_error_offset_proto(delta_offset, ret_proto);
|
|
offset += delta_offset;
|
|
|
|
if ((minor == 1) || (minor == 2)) {
|
|
/* parse inner protos */
|
|
ret_proto->inner_proto_offset = offset;
|
|
delta_offset = lua_parse_protos(ret_proto, buffer, header, offset, data_size);
|
|
lua_check_error_offset_proto(delta_offset, ret_proto);
|
|
offset += delta_offset;
|
|
|
|
/* specially handle recursive protos size */
|
|
ret_proto->inner_proto_size = offset - ret_proto->inner_proto_offset;
|
|
}
|
|
|
|
if (minor > 2) {
|
|
/* parse upvalues */
|
|
ret_proto->upvalue_offset = offset;
|
|
delta_offset = lua_parse_upvalues(ret_proto, buffer, offset, data_size, minor);
|
|
lua_check_error_offset_proto(delta_offset, ret_proto);
|
|
offset += delta_offset;
|
|
|
|
/* parse inner protos */
|
|
ret_proto->inner_proto_offset = offset;
|
|
delta_offset = lua_parse_protos(ret_proto, buffer, header, offset, data_size);
|
|
lua_check_error_offset_proto(delta_offset, ret_proto);
|
|
offset += delta_offset;
|
|
|
|
/* specially handle recursive protos size */
|
|
ret_proto->inner_proto_size = offset - ret_proto->inner_proto_offset;
|
|
}
|
|
|
|
if (minor == 5) {
|
|
/* parse proto name of main proto */
|
|
delta_offset = lua_parse_name(ret_proto, buffer, offset, data_size, minor);
|
|
lua_check_error_offset_proto(delta_offset, ret_proto);
|
|
|
|
if (ret_proto->proto_name) {
|
|
header->src_file_name = rz_str_dup((char *)ret_proto->proto_name);
|
|
}
|
|
|
|
offset += delta_offset;
|
|
}
|
|
|
|
if (minor == 2) {
|
|
/* parse upvalues */
|
|
ret_proto->upvalue_offset = offset;
|
|
delta_offset = lua_parse_upvalues(ret_proto, buffer, offset, data_size, minor);
|
|
lua_check_error_offset_proto(delta_offset, ret_proto);
|
|
offset += delta_offset;
|
|
|
|
/* parse proto name of main proto */
|
|
delta_offset = lua_parse_name(ret_proto, buffer, offset, data_size, minor);
|
|
lua_check_error_offset_proto(delta_offset, ret_proto);
|
|
offset += delta_offset;
|
|
}
|
|
/* parse debug */
|
|
ret_proto->debug_offset = offset;
|
|
delta_offset = lua_parse_debug(ret_proto, buffer, offset, data_size, minor);
|
|
lua_check_error_offset_proto(delta_offset, ret_proto);
|
|
offset += delta_offset;
|
|
|
|
ret_proto->size = offset - base_offset + 1;
|
|
return ret_proto;
|
|
}
|
|
|
|
size_t parse_header(const RzBinFile *bf, LuaHeaderInfo *header) {
|
|
RzBuffer *buffer = bf->buf;
|
|
ut8 major_minor_version = 0x00;
|
|
|
|
ut64 offset = strlen(LUAC_MAGIC);
|
|
CHECK_SIZE;
|
|
|
|
rz_buf_read8_at(buffer, offset, &major_minor_version); /* 1-byte in fact */
|
|
const ut8 major = (ut8)(major_minor_version & 0xF0) >> 4;
|
|
const ut8 minor = (ut8)(major_minor_version & 0x0F);
|
|
|
|
if (major != 5) {
|
|
RZ_LOG_ERROR("currently support lua 5.x only\n");
|
|
return 0;
|
|
}
|
|
|
|
if (minor > 5) {
|
|
RZ_LOG_ERROR("lua 5.%c not support now\n", header->minor + '0');
|
|
return 0;
|
|
}
|
|
|
|
offset++;
|
|
CHECK_SIZE;
|
|
|
|
/* read header members from work buffer */
|
|
/* is official compiler (minor > 0) */
|
|
if (minor > 0) {
|
|
READ8_CHECK_SIZE(buffer, offset, header->format);
|
|
}
|
|
|
|
/* check luac data if minor > 2 */
|
|
if (minor > 2) {
|
|
offset += strlen(LUAC_DATA);
|
|
CHECK_SIZE;
|
|
} else {
|
|
READ8_CHECK_SIZE(buffer, offset, header->endianness);
|
|
}
|
|
|
|
/* get int size on 5.1, 5.2, 5.3, 5.5 */
|
|
if (minor == 5) {
|
|
st32 tmp = 0;
|
|
const ut64 size_offset = lua_parse_szint(buffer, &tmp, offset, bf->size, minor);
|
|
header->int_size = (ut32)tmp;
|
|
offset += size_offset;
|
|
CHECK_SIZE;
|
|
const ut32 test_valid = lua_load_int(buffer, offset);
|
|
(void)test_valid;
|
|
offset += header->int_size;
|
|
} else if (minor <= 3) { ///< TODO: ????? need 3?
|
|
READ8_CHECK_SIZE(buffer, offset, header->int_size);
|
|
header->integer_size = 4;
|
|
} else {
|
|
header->int_size = 4;
|
|
}
|
|
CHECK_SIZE;
|
|
|
|
/* get size_t size on 5.1, 5.2, 5.3 */
|
|
if (minor <= 3) {
|
|
READ8_CHECK_SIZE(buffer, offset, header->size_t_size);
|
|
}
|
|
|
|
/* get instruction size */
|
|
if (minor == 5) {
|
|
st32 tmp = 0;
|
|
const ut64 size_offset = lua_parse_szint(buffer, &tmp, offset, bf->size, minor);
|
|
header->instruction_size = (ut32)tmp;
|
|
offset += size_offset;
|
|
CHECK_SIZE;
|
|
|
|
const ut32 test_valid = lua_load_int(buffer, offset);
|
|
offset += header->instruction_size;
|
|
CHECK_SIZE;
|
|
|
|
if (test_valid != LUAC5_INT_VALIDATION) {
|
|
RZ_LOG_ERROR("Integer format does not match with the expected integer\n");
|
|
return 0;
|
|
}
|
|
} else {
|
|
READ8_CHECK_SIZE(buffer, offset, header->instruction_size);
|
|
}
|
|
|
|
if (minor == 0) {
|
|
READ8_CHECK_VAL(buffer, &offset, 6, "Wrong size of SIZE_OP");
|
|
READ8_CHECK_VAL(buffer, &offset, 8, "Wrong size of SIZE_A");
|
|
READ8_CHECK_VAL(buffer, &offset, 9, "Wrong size of SIZE_B");
|
|
READ8_CHECK_VAL(buffer, &offset, 9, "Wrong size of SIZE_C");
|
|
}
|
|
|
|
/* get lua integer size on luac > 5.3 */
|
|
if (minor == 5) {
|
|
st32 tmp = 0;
|
|
const ut64 size_offset = lua_parse_szint(buffer, &tmp, offset, bf->size, minor);
|
|
header->integer_size = (ut32)tmp;
|
|
offset += size_offset;
|
|
CHECK_SIZE;
|
|
const ut64 test_valid = lua_load_integer(buffer, offset);
|
|
offset += header->integer_size;
|
|
(void)test_valid;
|
|
CHECK_SIZE;
|
|
/*
|
|
if (test_valid != LUAC5_INT_VALIDATION) {
|
|
RZ_LOG_ERROR("Integer format does not match with the expected integer\n");
|
|
return NULL;
|
|
}
|
|
*/
|
|
} else if ((minor == 3) || (minor == 4)) {
|
|
READ8_CHECK_SIZE(buffer, offset, header->integer_size);
|
|
}
|
|
|
|
/* get lua number size */
|
|
if (minor == 5) {
|
|
st32 tmp = 0;
|
|
const ut64 size_offset = lua_parse_szint(buffer, &tmp, offset, bf->size, minor);
|
|
header->number_size = (ut32)tmp;
|
|
offset += size_offset;
|
|
CHECK_SIZE;
|
|
const double test_valid = lua_load_number(buffer, offset);
|
|
offset += header->number_size;
|
|
CHECK_SIZE;
|
|
if (test_valid != LUAC5_NUMBER_VALIDATION) {
|
|
RZ_LOG_ERROR("Number format does not match with the expected number\n");
|
|
return 0;
|
|
}
|
|
} else {
|
|
READ8_CHECK_SIZE(buffer, offset, header->number_size);
|
|
}
|
|
if (minor == 0) {
|
|
const double number_valid = lua_load_number(buffer, offset);
|
|
if (number_valid != LUAC0_NUMBER_VALIDATION) {
|
|
RZ_LOG_ERROR("Number format does not match with the expected number (expected: %f, actual: %f)\n", LUAC0_NUMBER_VALIDATION, number_valid);
|
|
return 0;
|
|
}
|
|
offset += header->number_size;
|
|
CHECK_SIZE;
|
|
}
|
|
|
|
/* check lua number is integral on 5.1, 5.2 */
|
|
if ((minor == 1) || (minor == 2)) {
|
|
if (!rz_buf_read8_at(buffer, offset, &header->is_number_integral)) {
|
|
return 0;
|
|
}
|
|
offset++;
|
|
if (header->is_number_integral > 0x01) {
|
|
header->is_openwrt = true;
|
|
if (!rz_buf_read8_at(buffer, offset, &header->is_number_integral)) {
|
|
return 0;
|
|
}
|
|
offset += 1;
|
|
}
|
|
}
|
|
if (minor == 2) {
|
|
offset += strlen(LUAC_DATA);
|
|
}
|
|
CHECK_SIZE;
|
|
|
|
/* check int and num values on 5.3, 5.4 */
|
|
if ((minor == 3) || (minor == 4)) {
|
|
ut64 integer_valid = lua_load_integer(buffer, offset);
|
|
if (integer_valid != LUAC_INT_VALIDATION) {
|
|
RZ_LOG_ERROR("Integer format does not match with the expected integer\n");
|
|
return 0;
|
|
}
|
|
offset += header->integer_size;
|
|
CHECK_SIZE;
|
|
double number_valid = lua_load_number(buffer, offset);
|
|
if (number_valid != LUAC_NUMBER_VALIDATION) {
|
|
RZ_LOG_ERROR("Number format does not match with the expected number (expected: %f, actual: %f)\n", LUAC_NUMBER_VALIDATION, number_valid);
|
|
return 0;
|
|
}
|
|
offset += header->number_size;
|
|
CHECK_SIZE;
|
|
}
|
|
|
|
header->psize = offset;
|
|
header->major = major;
|
|
header->minor = minor;
|
|
|
|
if (header->minor >= 3) {
|
|
/* size upvalues */
|
|
if ((header->minor == 3) || (header->minor == 4)) {
|
|
ut8 size_upvalues = 0;
|
|
READ8_CHECK_SIZE(buffer, offset, size_upvalues);
|
|
} else {
|
|
st32 size_upvalues = 0;
|
|
const ut64 delta_offset = lua_parse_szint(buffer, &size_upvalues, offset, bf->size, minor);
|
|
lua_check_error_offset(delta_offset);
|
|
offset += delta_offset;
|
|
}
|
|
}
|
|
/* parse source file name */
|
|
if ((minor == 2) || (minor == 5))
|
|
return offset;
|
|
|
|
int name_len;
|
|
lua_parse_string(buffer, ((ut8 **)&(header->src_file_name)), &name_len, offset, bf->size, header->minor, header->is_openwrt ? 4 : sizeof(LUA_NUMBER));
|
|
(void)name_len;
|
|
|
|
return offset;
|
|
}
|
|
|
|
RzBinInfo *lua_parse_bin_info(const RzBinFile *bf, const LuaHeaderInfo *header) {
|
|
/* Common Ret */
|
|
RzBinInfo *ret = RZ_NEW0(RzBinInfo);
|
|
if (!ret) {
|
|
return NULL;
|
|
}
|
|
|
|
ret->has_va = true;
|
|
ret->file = rz_str_dup(bf->file);
|
|
ret->type = rz_str_newf("Lua %c.%c compiled file", header->major + '0', header->minor + '0');
|
|
ret->bclass = rz_str_dup("Lua compiled file");
|
|
ret->rclass = rz_str_dup("luac");
|
|
ret->arch = rz_str_dup("luac");
|
|
ret->machine = rz_str_newf("Lua %c.%c VM%s", header->major + '0', header->minor + '0', header->is_openwrt ? " (openwrt)" : "");
|
|
ret->os = rz_str_newf("%c.%c", header->major + '0', header->minor + '0');
|
|
ret->cpu = rz_str_newf("%c.%c", header->major + '0', header->minor + '0');
|
|
ret->bits = 32;
|
|
|
|
/* official format */
|
|
if (header->format != LUAC_FORMAT) {
|
|
ret->compiler = rz_str_dup("Unofficial Lua Compiler");
|
|
return ret;
|
|
}
|
|
ret->compiler = rz_str_dup("Official Lua Compiler");
|
|
if ((header->minor == 2) || (header->minor == 1))
|
|
return ret;
|
|
|
|
/* Check Size */
|
|
// TODO : remove this check and process different compiler options
|
|
if (header->minor == 0) {
|
|
if ((header->instruction_size != sizeof(ut64)) ||
|
|
(header->number_size != sizeof(LUA_NUMBER))) {
|
|
RZ_LOG_ERROR("Size definition does not match with the expected size (minor = 0)\n");
|
|
return ret;
|
|
}
|
|
} else {
|
|
if ((header->instruction_size != sizeof(LUA_INSTRUCTION)) ||
|
|
(header->integer_size != sizeof(LUA_INTEGER)) ||
|
|
(header->number_size != sizeof(LUA_NUMBER))) {
|
|
RZ_LOG_ERROR("Size definition does not match with the expected size\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (header->minor < 4) {
|
|
if (header->size_t_size != sizeof(size_t)) {
|
|
RZ_LOG_ERROR("Size definition does not match with the expected size\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (header->minor == 4) {
|
|
if (header->int_size != sizeof(LUA_INT)) {
|
|
RZ_LOG_ERROR("Int size definition does not match with the expected size\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* put source file info into GUID */
|
|
ret->guid = rz_str_dup(header->src_file_name ? header->src_file_name + 1 : "stripped");
|
|
return ret;
|
|
}
|