Add SBOM support

This commit is contained in:
pancake committed 2026-04-17 12:55:58 +02:00
1 parent 8731421d93
commit cdf1feec76
3 files changed
+378 -2

No files matched your search

+160
View File
@@ -297,6 +297,166 @@ R_API Il2CppImageDefinition *r2unity_get_images (R2UnityMetadata *meta, size_t *
return imgs;
}
static void get_assemblies_section (R2UnityMetadata *meta, ut64 *out_off, ut64 *out_size) {
if (meta->version < 27) {
*out_off = (ut64) meta->header.v24.assembliesOffset;
*out_size = (ut64) meta->header.v24.assembliesSize;
} else if (meta->version < 29) {
*out_off = (ut64) meta->header.v27.assembliesOffset;
*out_size = (ut64) meta->header.v27.assembliesSize;
} else {
*out_off = (ut64) meta->header.v29.assembliesOffset;
*out_size = (ut64) meta->header.v29.assembliesSize;
}
}
static void get_referenced_section (R2UnityMetadata *meta, ut64 *out_off, ut64 *out_size) {
if (meta->version < 27) {
*out_off = (ut64) meta->header.v24.referencedAssembliesOffset;
*out_size = (ut64) meta->header.v24.referencedAssembliesSize;
} else if (meta->version < 29) {
*out_off = (ut64) meta->header.v27.referencedAssembliesOffset;
*out_size = (ut64) meta->header.v27.referencedAssembliesSize;
} else {
*out_off = (ut64) meta->header.v29.referencedAssembliesOffset;
*out_size = (ut64) meta->header.v29.referencedAssembliesSize;
}
}
R_API Il2CppAssemblyDefinition *r2unity_get_assemblies (R2UnityMetadata *meta, size_t *count) {
if (!meta || !count) {
return NULL;
}
*count = 0;
ut64 aoff = 0, asize = 0;
get_assemblies_section (meta, &aoff, &asize);
if (!asize) {
return NULL;
}
/* Probe the per-row stride by joining against the images table:
* there is one assembly per image on every wire version. Expected
* strides are 56, 60, 64 or 68 bytes (see doc/sbom.md §4.1). */
size_t img_count = 0;
Il2CppImageDefinition *imgs = r2unity_get_images (meta, &img_count);
R_FREE (imgs);
if (!img_count) {
return NULL;
}
if (asize % img_count != 0) {
if (g_debug) {
fprintf (stderr, "[r2unity] assembliesSize=%"PFMT64u" not divisible by image_count=%u\n",
asize, (unsigned) img_count);
}
return NULL;
}
const ut64 entry = asize / img_count;
if (entry < 56 || entry > 80) {
if (g_debug) {
fprintf (stderr, "[r2unity] implausible assembly entry stride %"PFMT64u"\n", entry);
}
return NULL;
}
/* Within a row:
* [0..4) imageIndex
* [4..8) token (wire >= 24.1) OR customAttributeIndex (wire <= 24.0)
* [8..12) referencedAssemblyStart (wire >= 20)
* [12..16) referencedAssemblyCount
* [16..entry) Il2CppAssemblyNameDefinition
* aname size is (entry - 16): 48 when hashValueIndex dropped (>=24.4),
* 52 when it is still present (<=24.3). Layout within aname:
* nameIndex, cultureIndex, [hashValueIndex if 52], publicKeyIndex,
* hash_alg, hash_len, flags, major, minor, build, revision,
* public_key_token[8]. */
const ut64 aname_size = entry - 16;
bool has_hash_value = (aname_size == 52);
if (aname_size != 48 && aname_size != 52) {
if (g_debug) {
fprintf (stderr, "[r2unity] unexpected aname size %"PFMT64u"\n", aname_size);
}
return NULL;
}
*count = img_count;
Il2CppAssemblyDefinition *out = R_NEWS0 (Il2CppAssemblyDefinition, *count);
if (!out) {
return NULL;
}
ut8 *buf = R_NEWS (ut8, asize);
if (!buf) {
R_FREE (out);
return NULL;
}
if (r_buf_read_at (meta->buf, aoff, buf, asize) != (st64) asize) {
R_FREE (buf);
R_FREE (out);
return NULL;
}
for (size_t i = 0; i < *count; i++) {
const ut8 *p = buf + i * entry;
out[i].image_index = (int32_t) r_read_le32 (p + 0);
out[i].token = (meta->version >= 24) ? r_read_le32 (p + 4) : 0;
out[i].referenced_start = (int32_t) r_read_le32 (p + 8);
out[i].referenced_count = (int32_t) r_read_le32 (p + 12);
const ut8 *an = p + 16;
out[i].aname.name_idx = r_read_le32 (an + 0);
out[i].aname.culture_idx = r_read_le32 (an + 4);
ut64 o;
if (has_hash_value) {
out[i].aname.hash_value_idx = r_read_le32 (an + 8);
out[i].aname.public_key_idx = r_read_le32 (an + 12);
o = 16;
} else {
out[i].aname.hash_value_idx = 0;
out[i].aname.public_key_idx = r_read_le32 (an + 8);
o = 12;
}
out[i].aname.hash_alg = r_read_le32 (an + o); o += 4;
out[i].aname.hash_len = (int32_t) r_read_le32 (an + o); o += 4;
out[i].aname.flags = r_read_le32 (an + o); o += 4;
out[i].aname.major = (int32_t) r_read_le32 (an + o); o += 4;
out[i].aname.minor = (int32_t) r_read_le32 (an + o); o += 4;
out[i].aname.build = (int32_t) r_read_le32 (an + o); o += 4;
out[i].aname.revision = (int32_t) r_read_le32 (an + o); o += 4;
memcpy (out[i].aname.public_key_token, an + o, 8);
}
R_FREE (buf);
return out;
}
R_API int32_t *r2unity_get_referenced_assemblies (R2UnityMetadata *meta, size_t *count) {
if (!meta || !count) {
return NULL;
}
*count = 0;
ut64 roff = 0, rsize = 0;
get_referenced_section (meta, &roff, &rsize);
if (rsize < 4 || (rsize % 4) != 0) {
return NULL;
}
*count = (size_t) (rsize / 4);
int32_t *out = R_NEWS (int32_t, *count);
if (!out) {
*count = 0;
return NULL;
}
ut8 *buf = R_NEWS (ut8, rsize);
if (!buf) {
R_FREE (out);
*count = 0;
return NULL;
}
if (r_buf_read_at (meta->buf, roff, buf, rsize) != (st64) rsize) {
R_FREE (buf);
R_FREE (out);
*count = 0;
return NULL;
}
for (size_t i = 0; i < *count; i++) {
out[i] = (int32_t) r_read_le32 (buf + i * 4);
}
R_FREE (buf);
return out;
}
R_API bool r2unity_read_method_pointers_at (R2UnityMetadata *meta, const char *exe_path, ut64 addr, size_t count, ut64 **out_ptrs) {
(void) meta; (void) exe_path; (void) addr; (void) count; (void) out_ptrs;
return false;
+25
View File
@@ -290,12 +290,37 @@ typedef struct {
uint32_t customAttributeCount;
} Il2CppImageDefinition;
typedef struct {
uint32_t name_idx;
uint32_t culture_idx;
uint32_t public_key_idx;
uint32_t hash_value_idx; /* 0 when not present on disk (wire >= 24.4) */
uint32_t hash_alg;
int32_t hash_len;
uint32_t flags;
int32_t major;
int32_t minor;
int32_t build;
int32_t revision;
uint8_t public_key_token[8];
} Il2CppAssemblyNameDefinition;
typedef struct {
int32_t image_index;
uint32_t token; /* 0 when not present (wire <= 24.0) */
int32_t referenced_start;
int32_t referenced_count;
Il2CppAssemblyNameDefinition aname;
} Il2CppAssemblyDefinition;
R_API R2UnityMetadata *r2unity_parse_metadata (RBuffer *buf);
R_API void r2unity_free_metadata (R2UnityMetadata *meta);
R_API const char *r2unity_get_string (R2UnityMetadata *meta, uint32_t index);
R_API Il2CppTypeDefinition *r2unity_get_type_definitions (R2UnityMetadata *meta, size_t *count);
R_API Il2CppMethodDefinition *r2unity_get_method_definitions (R2UnityMetadata *meta, size_t *count);
R_API Il2CppImageDefinition *r2unity_get_images (R2UnityMetadata *meta, size_t *count);
R_API Il2CppAssemblyDefinition *r2unity_get_assemblies (R2UnityMetadata *meta, size_t *count);
R_API int32_t *r2unity_get_referenced_assemblies (R2UnityMetadata *meta, size_t *count);
/* Simplified API: use format-specific finders, or manual read stub. */
R_API bool r2unity_read_method_pointers_at (R2UnityMetadata *meta, const char *exe_path, ut64 addr, size_t count, ut64 **out_ptrs);
R_API bool r2unity_find_method_pointers_macho (R2UnityMetadata *meta, const char *macho_path, ut64 **out_ptrs);
+193 -2
View File
@@ -56,7 +56,189 @@ static void build_method_attrs_string (char *o, size_t osz, unsigned int flags)
}
static void print_usage (const char *prog_name) {
fprintf (stderr, "Usage: %s [-j] [-q] [-l N] [-f] [-a 0xADDR] [-c N] [-v] <executable> <path/to/global-metadata.dat>\n", prog_name);
fprintf (stderr, "Usage: %s [-j] [-q] [-l N] [-f] [-a 0xADDR] [-c N] [-S] [-v] <executable> <path/to/global-metadata.dat>\n", prog_name);
}
static const char *unity_range_from_wire (int wire) {
switch (wire) {
case 21: return "5.3.0-5.3.5";
case 22: return "5.3.6-5.4";
case 23: return "5.5";
case 24: return "5.6-2020.1";
case 27: return "2020.2-2021.3";
case 29: return "2022.1-2022.3";
case 31: return "2023.x-6000.x";
default: return "unknown";
}
}
static void json_escape (FILE *f, const char *s) {
for (; s && *s; s++) {
unsigned char c = (unsigned char) *s;
switch (c) {
case '"': fputs ("\\\"", f); break;
case '\\': fputs ("\\\\", f); break;
case '\b': fputs ("\\b", f); break;
case '\f': fputs ("\\f", f); break;
case '\n': fputs ("\\n", f); break;
case '\r': fputs ("\\r", f); break;
case '\t': fputs ("\\t", f); break;
default:
if (c < 0x20) fprintf (f, "\\u%04x", c);
else fputc (c, f);
}
}
}
static int emit_sbom (R2UnityMetadata *meta, const char *exe_path, const char *metadata_path) {
size_t img_count = 0;
Il2CppImageDefinition *imgs = r2unity_get_images (meta, &img_count);
size_t asm_count = 0;
Il2CppAssemblyDefinition *asms = r2unity_get_assemblies (meta, &asm_count);
size_t ref_count = 0;
int32_t *refs = r2unity_get_referenced_assemblies (meta, &ref_count);
if (!asms || !asm_count) {
fprintf (stderr, "r2unity: unable to decode assemblies table for wire version %d\n",
meta->version);
R_FREE (imgs);
R_FREE (asms);
R_FREE (refs);
return 1;
}
FILE *f = stdout;
fprintf (f, "{\n");
fprintf (f, " \"bomFormat\": \"CycloneDX\",\n");
fprintf (f, " \"specVersion\": \"1.5\",\n");
fprintf (f, " \"version\": 1,\n");
fprintf (f, " \"metadata\": {\n");
fprintf (f, " \"tools\": {\n");
fprintf (f, " \"components\": [\n");
fprintf (f, " { \"type\": \"application\", \"name\": \"r2unity\" }\n");
fprintf (f, " ]\n");
fprintf (f, " },\n");
fprintf (f, " \"component\": {\n");
fprintf (f, " \"type\": \"application\",\n");
fprintf (f, " \"name\": \"");
json_escape (f, exe_path ? exe_path : "unity-build");
fprintf (f, "\",\n");
fprintf (f, " \"version\": \"%s\",\n", unity_range_from_wire (meta->version));
fprintf (f, " \"properties\": [\n");
fprintf (f, " { \"name\": \"unity.metadata.path\", \"value\": \"");
json_escape (f, metadata_path);
fprintf (f, "\" },\n");
fprintf (f, " { \"name\": \"unity.metadata.wire_version\", \"value\": \"%d\" },\n", meta->version);
fprintf (f, " { \"name\": \"unity.metadata.confidence\", \"value\": \"range\" }\n");
fprintf (f, " ]\n");
fprintf (f, " }\n");
fprintf (f, " },\n");
/* managed components */
fprintf (f, " \"components\": [\n");
for (size_t i = 0; i < asm_count; i++) {
Il2CppAssemblyDefinition *a = &asms[i];
char *name = (char *) r2unity_get_string (meta, a->aname.name_idx);
char *culture = (char *) r2unity_get_string (meta, a->aname.culture_idx);
const char *nm = name ? name : "";
const char *cl = (culture && *culture) ? culture : "neutral";
/* image (DLL filename) via a->image_index */
const char *img = "";
char *img_name_owned = NULL;
if (imgs && a->image_index >= 0 && (size_t) a->image_index < img_count) {
img_name_owned = (char *) r2unity_get_string (meta, imgs[a->image_index].nameIndex);
if (img_name_owned) img = img_name_owned;
}
char pkt_hex[17];
int empty = 1;
for (int k = 0; k < 8; k++) if (a->aname.public_key_token[k]) { empty = 0; break; }
if (empty) {
strcpy (pkt_hex, "null");
} else {
snprintf (pkt_hex, sizeof (pkt_hex), "%02x%02x%02x%02x%02x%02x%02x%02x",
a->aname.public_key_token[0], a->aname.public_key_token[1],
a->aname.public_key_token[2], a->aname.public_key_token[3],
a->aname.public_key_token[4], a->aname.public_key_token[5],
a->aname.public_key_token[6], a->aname.public_key_token[7]);
}
fprintf (f, " {\n");
fprintf (f, " \"bom-ref\": \"asm:");
json_escape (f, nm);
fprintf (f, ":%d.%d.%d.%d\",\n",
a->aname.major, a->aname.minor, a->aname.build, a->aname.revision);
fprintf (f, " \"type\": \"library\",\n");
fprintf (f, " \"name\": \"");
json_escape (f, nm);
fprintf (f, "\",\n");
fprintf (f, " \"version\": \"%d.%d.%d.%d\",\n",
a->aname.major, a->aname.minor, a->aname.build, a->aname.revision);
fprintf (f, " \"purl\": \"pkg:generic/unity/");
json_escape (f, nm);
fprintf (f, "@%d.%d.%d.%d\",\n",
a->aname.major, a->aname.minor, a->aname.build, a->aname.revision);
fprintf (f, " \"properties\": [\n");
fprintf (f, " { \"name\": \"dotnet.culture\", \"value\": \"");
json_escape (f, cl);
fprintf (f, "\" },\n");
fprintf (f, " { \"name\": \"dotnet.public_key_token\", \"value\": ");
if (empty) fprintf (f, "null"); else fprintf (f, "\"%s\"", pkt_hex);
fprintf (f, " },\n");
fprintf (f, " { \"name\": \"dotnet.hash_alg\", \"value\": \"0x%08x\" },\n", a->aname.hash_alg);
fprintf (f, " { \"name\": \"dotnet.flags\", \"value\": \"0x%08x\" },\n", a->aname.flags);
fprintf (f, " { \"name\": \"il2cpp.image\", \"value\": \"");
json_escape (f, img);
fprintf (f, "\" },\n");
fprintf (f, " { \"name\": \"il2cpp.image_index\", \"value\": \"%d\" },\n", a->image_index);
fprintf (f, " { \"name\": \"il2cpp.token\", \"value\": \"0x%08x\" }\n", a->token);
fprintf (f, " ]\n");
fprintf (f, " }%s\n", (i + 1 < asm_count) ? "," : "");
free (name);
free (culture);
free (img_name_owned);
}
fprintf (f, " ],\n");
/* dependency edges from referencedAssemblies */
fprintf (f, " \"dependencies\": [\n");
bool first_dep = true;
for (size_t i = 0; i < asm_count; i++) {
Il2CppAssemblyDefinition *a = &asms[i];
if (a->referenced_count <= 0 || a->referenced_start < 0) continue;
if ((size_t) (a->referenced_start + a->referenced_count) > ref_count) continue;
char *name = (char *) r2unity_get_string (meta, a->aname.name_idx);
const char *nm = name ? name : "";
if (!first_dep) fprintf (f, ",\n");
first_dep = false;
fprintf (f, " {\n \"ref\": \"asm:");
json_escape (f, nm);
fprintf (f, ":%d.%d.%d.%d\",\n \"dependsOn\": [",
a->aname.major, a->aname.minor, a->aname.build, a->aname.revision);
bool first_ref = true;
for (int k = 0; k < a->referenced_count; k++) {
int32_t ridx = refs[a->referenced_start + k];
if (ridx < 0 || (size_t) ridx >= asm_count) continue;
Il2CppAssemblyDefinition *r = &asms[ridx];
char *rname = (char *) r2unity_get_string (meta, r->aname.name_idx);
if (!first_ref) fprintf (f, ",");
first_ref = false;
fprintf (f, "\n \"asm:");
json_escape (f, rname ? rname : "");
fprintf (f, ":%d.%d.%d.%d\"",
r->aname.major, r->aname.minor, r->aname.build, r->aname.revision);
free (rname);
}
fprintf (f, "\n ]\n }");
free (name);
}
fprintf (f, "\n ]\n}\n");
R_FREE (imgs);
R_FREE (asms);
R_FREE (refs);
return 0;
}
int main (int argc, char *argv[]) {
@@ -67,13 +249,15 @@ int main (int argc, char *argv[]) {
ut64 gmp_addr = 0;
size_t gmp_count = 0;
bool fast = false;
bool sbom = false;
int opt;
while ((opt = getopt (argc, argv, "jqfvl:a:c:")) != -1) {
while ((opt = getopt (argc, argv, "jqfvSl:a:c:")) != -1) {
switch (opt) {
case 'j': json_one_line = true; break;
case 'q': quiet = true; break;
case 'f': fast = true; break;
case 'v': debug = true; break;
case 'S': sbom = true; break;
case 'l': limit = strtol (optarg, NULL, 0); break;
case 'a': gmp_addr = (ut64) strtoull (optarg, NULL, 0); break;
case 'c': gmp_count = (size_t) strtoull (optarg, NULL, 0); break;
@@ -105,6 +289,13 @@ int main (int argc, char *argv[]) {
return 1;
}
if (sbom) {
int rc = emit_sbom (meta, exe_path, metadata_path);
r2unity_free_metadata (meta);
r_unref (buf);
return rc;
}
size_t type_count = 0;
Il2CppTypeDefinition *types = r2unity_get_type_definitions (meta, &type_count);