mirror of
https://github.com/radareorg/r2unity.git
synced 2026-11-07 06:36:00 -10:00
Add SBOM support
This commit is contained in:
1 parent
8731421d93
commit
cdf1feec76
3 files changed
+378
-2
No files matched your search
+160
@@ -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;
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
Reference in new issue
Block a user