Expose MBN plugin functions for other Bin plugins.

This commit is contained in:
Rot127 2025-06-11 07:28:08 -05:00 committed by Rot127
parent 3200b9e836
commit 28bd9cfa24
4 changed files with 380 additions and 198 deletions

212
librz/bin/format/mbn/mbn.c Normal file
View file

@ -0,0 +1,212 @@
// SPDX-FileCopyrightText: 2015-2019 pancake <pancake@nopcode.org>
// SPDX-FileCopyrightText: 2025 Rot127 <unisono@quyllur.org>
// SPDX-License-Identifier: LGPL-3.0-only
/**
* \file The MBN file format is not publicly documented currently.
* It is used in Qualcomm firmware images to verify the firmware binaries against the root of trust.
* There are more than one version of the MBN file format.
* This implementation only handles the signature bundle for the Qualcomm secure boot mechanism.
* For details see:
* https://www.qualcomm.com/content/dam/qcomm-martech/dm-assets/documents/secure-boot-and-image-authentication-version_final.pdf
*
* Another, more complete, MBN implantation can be found here:
* https://github.com/openpst/libopenpst/blob/master/include/qualcomm/mbn.h
*/
#include "mbn.h"
#include <stddef.h>
bool mbn_read_sbl_header(RzBuffer *b, SblHeader *sb, ut64 *offset) {
return rz_buf_read_le32_offset(b, offset, &sb->image_id) &&
rz_buf_read_le32_offset(b, offset, &sb->version) &&
rz_buf_read_le32_offset(b, offset, &sb->paddr) &&
rz_buf_read_le32_offset(b, offset, &sb->vaddr) &&
rz_buf_read_le32_offset(b, offset, &sb->psize) &&
rz_buf_read_le32_offset(b, offset, &sb->code_pa) &&
rz_buf_read_le32_offset(b, offset, &sb->sign_va) &&
rz_buf_read_le32_offset(b, offset, &sb->sign_sz) &&
rz_buf_read_le32_offset(b, offset, &sb->cert_va) &&
rz_buf_read_le32_offset(b, offset, &sb->cert_sz);
}
bool mbn_check_buffer(RzBuffer *b) {
ut64 offset = 0;
SblHeader sb = { 0 };
rz_return_val_if_fail(b, false);
if (!mbn_read_sbl_header(b, &sb, &offset)) {
return false;
}
if (sb.version != 3) { // NAND
return false;
}
if (sb.paddr + sizeof(SblHeader) > offset) { // NAND
return false;
}
if (sb.vaddr < 0x100 || sb.psize > offset) { // NAND
return false;
}
if (sb.cert_va < sb.vaddr) {
return false;
}
if (sb.cert_sz >= 0xf0000) {
return false;
}
if (sb.sign_va < sb.vaddr) {
return false;
}
if (sb.sign_sz >= 0xf0000) {
return false;
}
if (sb.image_id == RZ_MBN_kMbnImageNone || sb.image_id > RZ_MBN_kMbnImageLast) {
return false;
}
return true;
}
bool mbn_load_buffer(RzBinFile *bf, RzBinObject *obj, RzBuffer *b, Sdb *sdb) {
ut64 offset = 0;
SblHeader *sb = RZ_NEW0(SblHeader);
if (!sb || !mbn_read_sbl_header(b, sb, &offset)) {
free(sb);
return false;
}
obj->bin_obj = sb;
return true;
}
ut64 mbn_baddr(RzBinFile *bf) {
SblHeader *sb = mbn_file_get_hdr(bf);
return sb->vaddr;
}
RzPVector /*<RzBinAddr *>*/ *mbn_entries(RzBinFile *bf) {
SblHeader *sb = mbn_file_get_hdr(bf);
RzPVector *ret = rz_pvector_new(free);
if (ret) {
RzBinAddr *ptr = RZ_NEW0(RzBinAddr);
if (ptr) {
ptr->paddr = 40 + sb->code_pa;
ptr->vaddr = 40 + sb->code_pa + sb->vaddr;
rz_pvector_push(ret, ptr);
}
}
return ret;
}
RzPVector /*<RzBinSection *>*/ *mbn_sections(RzBinFile *bf) {
SblHeader *sb = mbn_file_get_hdr(bf);
RzBinSection *ptr = NULL;
RzPVector *ret = NULL;
if (!(ret = rz_pvector_new(free))) {
return NULL;
}
// add text segment
if (!(ptr = RZ_NEW0(RzBinSection))) {
return ret;
}
ptr->name = rz_str_dup("text");
ptr->size = sb->psize;
ptr->vsize = sb->psize;
ptr->paddr = sb->paddr + 40;
ptr->vaddr = sb->vaddr;
ptr->perm = RZ_PERM_RX; // r-x
ptr->has_strings = true;
rz_pvector_push(ret, ptr);
if (!(ptr = RZ_NEW0(RzBinSection))) {
return ret;
}
ptr->name = rz_str_dup("sign");
ptr->size = sb->sign_sz;
ptr->vsize = sb->sign_sz;
ptr->paddr = sb->sign_va - sb->vaddr;
ptr->vaddr = sb->sign_va;
ptr->perm = RZ_PERM_R; // r--
ptr->has_strings = true;
rz_pvector_push(ret, ptr);
if (sb->cert_sz && sb->cert_va > sb->vaddr) {
if (!(ptr = RZ_NEW0(RzBinSection))) {
return ret;
}
ptr->name = rz_str_dup("cert");
ptr->size = sb->cert_sz;
ptr->vsize = sb->cert_sz;
ptr->paddr = sb->cert_va - sb->vaddr;
ptr->vaddr = sb->cert_va;
ptr->perm = RZ_PERM_R; // r--
ptr->has_strings = true;
rz_pvector_push(ret, ptr);
}
return ret;
}
RzBinInfo *mbn_info(RzBinFile *bf) {
RzBinInfo *ret = NULL;
const int bits = 16;
if (!(ret = RZ_NEW0(RzBinInfo))) {
return NULL;
}
ret->file = rz_str_dup(bf->file);
ret->bclass = rz_str_dup("bootloader");
ret->rclass = rz_str_dup("mbn");
ret->os = rz_str_dup("MBN");
ret->arch = rz_str_dup("arm");
ret->machine = rz_str_dup(ret->arch);
ret->subsystem = rz_str_dup("mbn");
ret->type = rz_str_dup("sbl"); // secondary boot loader
ret->bits = bits;
ret->has_va = true;
ret->has_crypto = true; // must be false if there' no sign or cert sections
ret->has_pi = false;
ret->has_nx = false;
ret->big_endian = false;
ret->dbg_info = false;
return ret;
}
ut64 mbn_size(RzBinFile *bf) {
SblHeader *sb = mbn_file_get_hdr(bf);
return sizeof(SblHeader) + sb->psize;
}
void mbn_destroy_obj(SblHeader *sb) {
free(sb);
}
void mbn_destroy(RzBinFile *bf) {
SblHeader *sb = mbn_file_get_hdr(bf);
mbn_destroy_obj(sb);
}
RzPVector /*<RzBinString *>*/ *mbn_strings(RzBinFile *bf) {
RzBinStringSearchOpt opt;
rz_bin_string_search_opt_init(&opt);
// we only search strings with a minimum length of 10 bytes.
opt.mode = RZ_BIN_STRING_SEARCH_MODE_READ_ONLY_SECTIONS;
opt.min_length = 10;
return rz_bin_file_strings(bf, &opt);
}
void mbn_header_obj(SblHeader *sb, RZ_NONNULL PrintfCallback cb) {
rz_return_if_fail(sb && cb);
cb("0x%02" PFMTSZx " image_id: %s (0x%" PFMT32x ")\n", offsetof(SblHeader, image_id), rz_mbn_image_id_str(sb->image_id), sb->image_id);
cb("0x%02" PFMTSZx " version: 0x%" PFMT32x "\n", offsetof(SblHeader, version), sb->version);
cb("0x%02" PFMTSZx " paddr: 0x%" PFMT32x "\n", offsetof(SblHeader, paddr), sb->paddr);
cb("0x%02" PFMTSZx " vaddr: 0x%" PFMT32x "\n", offsetof(SblHeader, vaddr), sb->vaddr);
cb("0x%02" PFMTSZx " psize: 0x%" PFMT32x "\n", offsetof(SblHeader, psize), sb->psize);
cb("0x%02" PFMTSZx " code_pa: 0x%" PFMT32x "\n", offsetof(SblHeader, code_pa), sb->code_pa);
cb("0x%02" PFMTSZx " sign_va: 0x%" PFMT32x "\n", offsetof(SblHeader, sign_va), sb->sign_va);
cb("0x%02" PFMTSZx " sign_sz: 0x%" PFMT32x "\n", offsetof(SblHeader, sign_sz), sb->sign_sz);
cb("0x%02" PFMTSZx " cert_va: 0x%" PFMT32x "\n", offsetof(SblHeader, cert_va), sb->cert_va);
cb("0x%02" PFMTSZx " cert_sz: 0x%" PFMT32x "\n", offsetof(SblHeader, cert_sz), sb->cert_sz);
}
void mbn_header(RzBinFile *bf) {
rz_return_if_fail(bf && bf->o && bf->o->bin_obj && bf->rbin && bf->rbin->cb_printf);
SblHeader *sb = mbn_file_get_hdr(bf);
mbn_header_obj(sb, bf->rbin->cb_printf);
}

156
librz/bin/format/mbn/mbn.h Normal file
View file

@ -0,0 +1,156 @@
// SPDX-FileCopyrightText: 2015-2019 pancake <pancake@nopcode.org>
// SPDX-FileCopyrightText: 2025 Rot127 <unisono@quyllur.org>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_types.h>
#include <rz_util.h>
#include <rz_lib.h>
#include <rz_bin.h>
#define mbn_file_get_hdr(bf) ((SblHeader *)bf->o->bin_obj)
/**
* \brief A 40 bytes MBN header for a chain of attestation.
* For a more general intro to MBN, see the file comment in mbn.c.
*
* For firmware attestation scheme details see:
* https://www.qualcomm.com/content/dam/qcomm-martech/dm-assets/documents/secure-boot-and-image-authentication-version_final.pdf
*/
typedef struct sbl_header {
ut32 /*<RzMBNImageId>*/ image_id; ///< Identifies the type of image this header represents (OEM SBL, AMSS, Apps boot loader, etc.).
ut32 version; ///< Header version
ut32 paddr; ///< Offset from the _end of the header_ where the image starts.
ut32 vaddr; ///< Memory address it is loaded at.
ut32 psize; ///< Size of complete image in bytes.
ut32 code_pa; ///< Size of code region in bytes.
ut32 sign_va; ///< Memory address to the attestation signature.
ut32 sign_sz; ///< Size of the attestation signature in bytes.
ut32 cert_va; ///< Memory address to the chain of attestation of this image.
ut32 cert_sz; ///< Size of the attestation chain in bytes.
} SblHeader;
/**
* \brief Known MBN image IDs.
*
* Reference:
* https://github.com/openpst/libopenpst/blob/master/include/qualcomm/mbn.h
*/
typedef enum {
RZ_MBN_kMbnImageNone = 0x00,
RZ_MBN_kMbnImageOemSbl = 0x01,
RZ_MBN_kMbnImageAmss = 0x02,
RZ_MBN_kMbnImageOcbl = 0x03,
RZ_MBN_kMbnImageHash = 0x04,
RZ_MBN_kMbnImageAppbl = 0x05,
RZ_MBN_kMbnImageApps = 0x06,
RZ_MBN_kMbnImageHostDl = 0x07,
RZ_MBN_kMbnImageDsp1 = 0x08,
RZ_MBN_kMbnImageFsbl = 0x09,
RZ_MBN_kMbnImageDbl = 0x0A,
RZ_MBN_kMbnImageOsbl = 0x0B,
RZ_MBN_kMbnImageDsp2 = 0x0C,
RZ_MBN_kMbnImageEhostdl = 0x0D,
RZ_MBN_kMbnImageNandprg = 0x0E,
RZ_MBN_kMbnImageNorprg = 0x0F,
RZ_MBN_kMbnImageRamfs1 = 0x10,
RZ_MBN_kMbnImageRamfs2 = 0x11,
RZ_MBN_kMbnImageAdspQ5 = 0x12,
RZ_MBN_kMbnImageAppsKernel = 0x13,
RZ_MBN_kMbnImageBackupRamfs = 0x14,
RZ_MBN_kMbnImageSbl1 = 0x15,
RZ_MBN_kMbnImageSbl2 = 0x16,
RZ_MBN_kMbnImageRpm = 0x17,
RZ_MBN_kMbnImageSbl3 = 0x18,
RZ_MBN_kMbnImageTz = 0x19,
RZ_MBN_kMbnImageSsdKeys = 0x1A,
RZ_MBN_kMbnImageGen = 0x1B,
RZ_MBN_kMbnImageDsp3 = 0x1C,
RZ_MBN_kMbnImageAcdb = 0x1D,
RZ_MBN_kMbnImageWdt = 0x1E,
RZ_MBN_kMbnImageMba = 0x1F,
RZ_MBN_kMbnImageLast = RZ_MBN_kMbnImageMba
} RzMBNImageId;
static inline const char *rz_mbn_image_id_str(RzMBNImageId id) {
switch (id) {
case RZ_MBN_kMbnImageNone:
return "kMbnImageNone";
case RZ_MBN_kMbnImageOemSbl:
return "kMbnImageOemSbl";
case RZ_MBN_kMbnImageAmss:
return "kMbnImageAmss";
case RZ_MBN_kMbnImageOcbl:
return "kMbnImageOcbl";
case RZ_MBN_kMbnImageHash:
return "kMbnImageHash";
case RZ_MBN_kMbnImageAppbl:
return "kMbnImageAppbl";
case RZ_MBN_kMbnImageApps:
return "kMbnImageApps";
case RZ_MBN_kMbnImageHostDl:
return "kMbnImageHostDl";
case RZ_MBN_kMbnImageDsp1:
return "kMbnImageDsp1";
case RZ_MBN_kMbnImageFsbl:
return "kMbnImageFsbl";
case RZ_MBN_kMbnImageDbl:
return "kMbnImageDbl";
case RZ_MBN_kMbnImageOsbl:
return "kMbnImageOsbl";
case RZ_MBN_kMbnImageDsp2:
return "kMbnImageDsp2";
case RZ_MBN_kMbnImageEhostdl:
return "kMbnImageEhostdl";
case RZ_MBN_kMbnImageNandprg:
return "kMbnImageNandprg";
case RZ_MBN_kMbnImageNorprg:
return "kMbnImageNorprg";
case RZ_MBN_kMbnImageRamfs1:
return "kMbnImageRamfs1";
case RZ_MBN_kMbnImageRamfs2:
return "kMbnImageRamfs2";
case RZ_MBN_kMbnImageAdspQ5:
return "kMbnImageAdspQ5";
case RZ_MBN_kMbnImageAppsKernel:
return "kMbnImageAppsKernel";
case RZ_MBN_kMbnImageBackupRamfs:
return "kMbnImageBackupRamfs";
case RZ_MBN_kMbnImageSbl1:
return "kMbnImageSbl1";
case RZ_MBN_kMbnImageSbl2:
return "kMbnImageSbl2";
case RZ_MBN_kMbnImageRpm:
return "kMbnImageRpm";
case RZ_MBN_kMbnImageSbl3:
return "kMbnImageSbl3";
case RZ_MBN_kMbnImageTz:
return "kMbnImageTz";
case RZ_MBN_kMbnImageSsdKeys:
return "kMbnImageSsdKeys";
case RZ_MBN_kMbnImageGen:
return "kMbnImageGen";
case RZ_MBN_kMbnImageDsp3:
return "kMbnImageDsp3";
case RZ_MBN_kMbnImageAcdb:
return "kMbnImageAcdb";
case RZ_MBN_kMbnImageWdt:
return "kMbnImageWdt";
case RZ_MBN_kMbnImageMba:
return "kMbnImageMba";
}
return "unknown";
}
bool mbn_read_sbl_header(RzBuffer *b, SblHeader *sb, ut64 *offset);
bool mbn_check_buffer(RzBuffer *b);
bool mbn_load_buffer(RzBinFile *bf, RzBinObject *obj, RzBuffer *b, Sdb *sdb);
ut64 mbn_baddr(RzBinFile *bf);
RzPVector /*<RzBinAddr *>*/ *mbn_entries(RzBinFile *bf);
RzPVector /*<RzBinSection *>*/ *mbn_sections(RzBinFile *bf);
RzBinInfo *mbn_info(RzBinFile *bf);
ut64 mbn_size(RzBinFile *bf);
void mbn_destroy_obj(SblHeader *sb);
void mbn_destroy(RzBinFile *bf);
RzPVector /*<RzBinString *>*/ *mbn_strings(RzBinFile *bf);
void mbn_header_obj(SblHeader *sb, RZ_NONNULL PrintfCallback cb);
void mbn_header(RzBinFile *bf);

View file

@ -215,6 +215,7 @@ rz_bin_sources = [
'format/mach0/mach064.c',
'format/mach0/mach064_relocs.c',
'format/mach0/mach064_chained_fixups.c',
'format/mbn/mbn.c',
'format/xnu/rz_cf_dict.c',
'format/mdmp/mdmp.c',
'format/mdmp/mdmp_pe.c',

View file

@ -1,211 +1,24 @@
// SPDX-FileCopyrightText: 2015-2019 pancake <pancake@nopcode.org>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_types.h>
#include <rz_util.h>
#include <rz_lib.h>
#include <rz_bin.h>
#define mbn_file_get_hdr(bf) ((SblHeader *)bf->o->bin_obj)
typedef struct sbl_header {
ut32 load_index;
ut32 version; // (flash_partition_version) 3 = nand
ut32 paddr; // This + 40 is the start of the code in the file
ut32 vaddr; // Where it's loaded in memory
ut32 psize; // code_size + signature_size + cert_chain_size
ut32 code_pa; // Only what's loaded to memory
ut32 sign_va;
ut32 sign_sz;
ut32 cert_va; // Max of 3 certs?
ut32 cert_sz;
} SblHeader;
static bool read_sbl_header(RzBuffer *b, SblHeader *sb, ut64 *offset) {
return rz_buf_read_le32_offset(b, offset, &sb->load_index) &&
rz_buf_read_le32_offset(b, offset, &sb->version) &&
rz_buf_read_le32_offset(b, offset, &sb->paddr) &&
rz_buf_read_le32_offset(b, offset, &sb->vaddr) &&
rz_buf_read_le32_offset(b, offset, &sb->psize) &&
rz_buf_read_le32_offset(b, offset, &sb->code_pa) &&
rz_buf_read_le32_offset(b, offset, &sb->sign_va) &&
rz_buf_read_le32_offset(b, offset, &sb->sign_sz) &&
rz_buf_read_le32_offset(b, offset, &sb->cert_va) &&
rz_buf_read_le32_offset(b, offset, &sb->cert_sz);
}
static bool check_buffer(RzBuffer *b) {
ut64 offset = 0;
SblHeader sb = { 0 };
rz_return_val_if_fail(b, false);
if (!read_sbl_header(b, &sb, &offset)) {
return false;
}
if (sb.version != 3) { // NAND
return false;
}
if (sb.paddr + sizeof(SblHeader) > offset) { // NAND
return false;
}
if (sb.vaddr < 0x100 || sb.psize > offset) { // NAND
return false;
}
if (sb.cert_va < sb.vaddr) {
return false;
}
if (sb.cert_sz >= 0xf0000) {
return false;
}
if (sb.sign_va < sb.vaddr) {
return false;
}
if (sb.sign_sz >= 0xf0000) {
return false;
}
if (sb.load_index < 1 || sb.load_index > 0x40) {
return false; // should be 0x19 ?
}
return true;
}
static bool load_buffer(RzBinFile *bf, RzBinObject *obj, RzBuffer *b, Sdb *sdb) {
ut64 offset = 0;
SblHeader *sb = RZ_NEW0(SblHeader);
if (!sb || !read_sbl_header(b, sb, &offset)) {
free(sb);
return false;
}
obj->bin_obj = sb;
return true;
}
static ut64 baddr(RzBinFile *bf) {
SblHeader *sb = mbn_file_get_hdr(bf);
return sb->vaddr;
}
static RzPVector /*<RzBinAddr *>*/ *entries(RzBinFile *bf) {
SblHeader *sb = mbn_file_get_hdr(bf);
RzPVector *ret = rz_pvector_new(free);
if (ret) {
RzBinAddr *ptr = RZ_NEW0(RzBinAddr);
if (ptr) {
ptr->paddr = 40 + sb->code_pa;
ptr->vaddr = 40 + sb->code_pa + sb->vaddr;
rz_pvector_push(ret, ptr);
}
}
return ret;
}
static RzPVector /*<RzBinSection *>*/ *sections(RzBinFile *bf) {
SblHeader *sb = mbn_file_get_hdr(bf);
RzBinSection *ptr = NULL;
RzPVector *ret = NULL;
if (!(ret = rz_pvector_new(free))) {
return NULL;
}
// add text segment
if (!(ptr = RZ_NEW0(RzBinSection))) {
return ret;
}
ptr->name = rz_str_dup("text");
ptr->size = sb->psize;
ptr->vsize = sb->psize;
ptr->paddr = sb->paddr + 40;
ptr->vaddr = sb->vaddr;
ptr->perm = RZ_PERM_RX; // r-x
ptr->has_strings = true;
rz_pvector_push(ret, ptr);
if (!(ptr = RZ_NEW0(RzBinSection))) {
return ret;
}
ptr->name = rz_str_dup("sign");
ptr->size = sb->sign_sz;
ptr->vsize = sb->sign_sz;
ptr->paddr = sb->sign_va - sb->vaddr;
ptr->vaddr = sb->sign_va;
ptr->perm = RZ_PERM_R; // r--
ptr->has_strings = true;
rz_pvector_push(ret, ptr);
if (sb->cert_sz && sb->cert_va > sb->vaddr) {
if (!(ptr = RZ_NEW0(RzBinSection))) {
return ret;
}
ptr->name = rz_str_dup("cert");
ptr->size = sb->cert_sz;
ptr->vsize = sb->cert_sz;
ptr->paddr = sb->cert_va - sb->vaddr;
ptr->vaddr = sb->cert_va;
ptr->perm = RZ_PERM_R; // r--
ptr->has_strings = true;
rz_pvector_push(ret, ptr);
}
return ret;
}
static RzBinInfo *info(RzBinFile *bf) {
RzBinInfo *ret = NULL;
const int bits = 16;
if (!(ret = RZ_NEW0(RzBinInfo))) {
return NULL;
}
ret->file = rz_str_dup(bf->file);
ret->bclass = rz_str_dup("bootloader");
ret->rclass = rz_str_dup("mbn");
ret->os = rz_str_dup("MBN");
ret->arch = rz_str_dup("arm");
ret->machine = rz_str_dup(ret->arch);
ret->subsystem = rz_str_dup("mbn");
ret->type = rz_str_dup("sbl"); // secondary boot loader
ret->bits = bits;
ret->has_va = true;
ret->has_crypto = true; // must be false if there' no sign or cert sections
ret->has_pi = false;
ret->has_nx = false;
ret->big_endian = false;
ret->dbg_info = false;
return ret;
}
static ut64 size(RzBinFile *bf) {
SblHeader *sb = mbn_file_get_hdr(bf);
return sizeof(SblHeader) + sb->psize;
}
static void destroy(RzBinFile *bf) {
SblHeader *sb = mbn_file_get_hdr(bf);
free(sb);
}
static RzPVector /*<RzBinString *>*/ *strings(RzBinFile *bf) {
RzBinStringSearchOpt opt;
rz_bin_string_search_opt_init(&opt);
// we only search strings with a minimum length of 10 bytes.
opt.mode = RZ_BIN_STRING_SEARCH_MODE_READ_ONLY_SECTIONS;
opt.min_length = 10;
return rz_bin_file_strings(bf, &opt);
}
#include "../format/mbn/mbn.h"
RzBinPlugin rz_bin_plugin_mbn = {
.name = "mbn",
.desc = "MBN/SBL bootloader binary",
.license = "LGPL3",
.author = "pancake",
.load_buffer = &load_buffer,
.destroy = &destroy,
.size = &size,
.check_buffer = &check_buffer,
.baddr = &baddr,
.entries = &entries,
.load_buffer = &mbn_load_buffer,
.destroy = &mbn_destroy,
.header = &mbn_header,
.size = &mbn_size,
.check_buffer = &mbn_check_buffer,
.baddr = &mbn_baddr,
.entries = &mbn_entries,
.maps = &rz_bin_maps_of_file_sections,
.sections = &sections,
.strings = &strings,
.info = &info,
.sections = &mbn_sections,
.strings = &mbn_strings,
.info = &mbn_info,
};
#ifndef RZ_PLUGIN_INCORE