CRO0: Difference between revisions
mNo edit summary |
Trie entries + unknown fields |
||
| Line 48: | Line 48: | ||
| 0x98 | | 0x98 | ||
| 0x04 | | 0x04 | ||
| | | Fixed size, set by RO after fixing, used to keep track of the new size | ||
|- | |- | ||
| 0x9C | | 0x9C | ||
| Line 56: | Line 56: | ||
| 0xA0 | | 0xA0 | ||
| 0x04 | | 0x04 | ||
| [[#Segment offset | | [[#Segment offset|Segment offset]] that is always the same as export symbol "nnroControlObject_". 0xFFFFFFFF in CRS | ||
|- | |- | ||
| 0xA4 | | 0xA4 | ||
| 0x04 | | 0x04 | ||
| [[#Segment offset | | [[#Segment offset|Segment offset]] for "OnLoad" function, which will be called when the module is initialized. Set to 0xFFFFFFFF if not exists. | ||
|- | |- | ||
| 0xA8 | | 0xA8 | ||
| 0x04 | | 0x04 | ||
| [[#Segment offset | | [[#Segment offset|Segment offset]] for "OnExit" function, which will be called when the module is finalized. Set to 0xFFFFFFFF if not exists. | ||
|- | |- | ||
| 0xAC | | 0xAC | ||
| 0x04 | | 0x04 | ||
| [[#Segment offset | | [[#Segment offset|Segment offset]] for "OnUnresolved" function, which will be called when an unresolved function is called. Set to 0xFFFFFFFF if not exists. | ||
|- | |- | ||
| 0xB0 | | 0xB0 | ||
| Line 100: | Line 100: | ||
| 0xCC | | 0xCC | ||
| 0x04 | | 0x04 | ||
| Segment Table num (size = num*12) | | Segment Table num (size = num * 12) | ||
|- | |- | ||
| 0xD0 | | 0xD0 | ||
| Line 128: | Line 128: | ||
| 0xE8 | | 0xE8 | ||
| 0x04 | | 0x04 | ||
| Export | | Export Trie offset | ||
|- | |- | ||
| 0xEC | | 0xEC | ||
| 0x04 | | 0x04 | ||
| Export | | Export Trie num (size = num * 8) | ||
|- | |- | ||
| 0xF0 | | 0xF0 | ||
| Line 144: | Line 144: | ||
| 0xF8 | | 0xF8 | ||
| 0x04 | | 0x04 | ||
| Import | | Import Relocations offset | ||
|- | |- | ||
| 0xFC | | 0xFC | ||
| 0x04 | | 0x04 | ||
| Import | | Import Relocations num (size = num * 12) | ||
|- | |- | ||
| 0x100 | | 0x100 | ||
| Line 184: | Line 184: | ||
| 0x120 | | 0x120 | ||
| 0x04 | | 0x04 | ||
| | | [[#Fields 0x120, 0x130|Unknown Relocations]] base parameter offset | ||
|- | |- | ||
| 0x124 | | 0x124 | ||
| 0x04 | | 0x04 | ||
| | | [[#Fields 0x120, 0x130|Unknown Relocations]] base parameter num (size = num * 8) | ||
|- | |- | ||
| 0x128 | | 0x128 | ||
| 0x04 | | 0x04 | ||
| | | Internal Relocations offset | ||
|- | |- | ||
| 0x12C | | 0x12C | ||
| 0x04 | | 0x04 | ||
| | | Internal Relocations num (size = num * 12) | ||
|- | |- | ||
| 0x130 | | 0x130 | ||
| 0x04 | | 0x04 | ||
| | | [[#Fields 0x120, 0x130|Unknown Relocations]] offset | ||
|- | |- | ||
| 0x134 | | 0x134 | ||
| 0x04 | | 0x04 | ||
| | | [[#Fields 0x120, 0x130|Unknown Relocations]] num (size = num * 12) | ||
|} | |} | ||
==Segment offset | == Segment offset == | ||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
| Line 220: | Line 220: | ||
|} | |} | ||
==Segment Table entry | == Segment Table entry == | ||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
! Offset | ! Offset | ||
| Line 239: | Line 239: | ||
|} | |} | ||
==Named Export Table entry | == Named Export Table entry == | ||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
! Offset | ! Offset | ||
| Line 254: | Line 254: | ||
|} | |} | ||
==Indexed Export Table entry | == Indexed Export Table entry == | ||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
! Offset | ! Offset | ||
| Line 265: | Line 265: | ||
|} | |} | ||
==Named Import Table entry | == Export Trie entry == | ||
Each entry represents a node in the trie: | |||
{| class="wikitable" border="1" | |||
! Offset | |||
! Size | |||
! Description | |||
|- | |||
| 0x0 | |||
| 0x2 | |||
| Flags (bits 0-2: bit index, bits 3-15: char index) | |||
|- | |||
| 0x2 | |||
| 0x2 | |||
| Left node info (bit 0-14: node index, bit 15: is leaf) | |||
|- | |||
| 0x4 | |||
| 0x2 | |||
| Right node info (same as left node) | |||
|- | |||
| 0x6 | |||
| 0x2 | |||
| Index for the named exports table | |||
|} | |||
The root node is a dummy node, with the right child being a leaf and pointing to the empty string. Hence the search must be performed starting from the left child. | |||
Search is performed by extracting the bit of the input using informations encoded in the flags. If the bit is 0 or the offsets are out of bounds the search continues on the left, else on the right. | |||
The search always yields a result, which may not match the input string, so a final strcmp call is required. | |||
== Named Import Table entry == | |||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
! Offset | ! Offset | ||
| Line 280: | Line 311: | ||
|} | |} | ||
==Indexed Import Table entry | == Indexed Import Table entry == | ||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
! Offset | ! Offset | ||
| Line 295: | Line 326: | ||
|} | |} | ||
==Anonymous Import Table entry | == Anonymous Import Table entry == | ||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
! Offset | ! Offset | ||
| Line 310: | Line 341: | ||
|} | |} | ||
==Import Module Table entry | == Import Module Table entry == | ||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
! Offset | ! Offset | ||
| Line 337: | Line 368: | ||
|} | |} | ||
== | == Relocation entry == | ||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
! Offset | ! Offset | ||
| Line 349: | Line 380: | ||
| 0x4 | | 0x4 | ||
| 0x1 | | 0x1 | ||
| | | Relocation type (R_ARM_NONE = 0, R_ARM_ABS32 = 2, R_ARM_REL32 = 3, R_ARM_THM_PC22 = 10, R_ARM_CALL = 28, R_ARM_JUMP24 = 29, R_ARM_TARGET1 = 38, R_ARM_PREL31 = 42) | ||
|- | |- | ||
| 0x5 | | 0x5 | ||
| 0x1 | | 0x1 | ||
| For import | | For import relocations, non-zero if last entry; for internal relocations, this is the referred segment index | ||
|- | |- | ||
| 0x6 | | 0x6 | ||
| 0x1 | | 0x1 | ||
| For import | | For import relocations, 1 is written to first entry if all symbols loaded successfully; unknown (padding?) for internal relocations | ||
|- | |- | ||
| 0x7 | | 0x7 | ||
| Line 370: | Line 401: | ||
Relocation code from RO: | Relocation code from RO: | ||
static Result | static Result writeRelocation(u32* out, u32 relocType, u32 addend, u32 base, u32 inputPtr) { | ||
const s32 branchOffset = inputPtr - base; | const s32 branchOffset = inputPtr - base; | ||
u32 offset = base + addend - inputPtr; | u32 offset = base + addend - inputPtr; | ||
if ( | if (relocType == R_ARM_NONE) | ||
return 0; | return 0; | ||
if ( | if (relocType == R_ARM_ABS32 || relocType == R_ARM_TARGET1) { | ||
*out = base + addend; | *out = base + addend; | ||
return 0; | return 0; | ||
} | } | ||
if ( | if (relocType == R_ARM_REL32) { | ||
*out = offset; | *out = offset; | ||
return 0; | return 0; | ||
} | } | ||
if ( | if (relocType == R_ARM_THM_PC22) { | ||
// +-4MB. | // +-4MB. | ||
if (branchOffset >= 0x400000 || branchOffset <= -0x400000) | if (branchOffset >= 0x400000 || branchOffset <= -0x400000) | ||
| Line 404: | Line 435: | ||
} | } | ||
if ( | if (relocType == R_ARM_CALL) { | ||
// +-32MB. | // +-32MB. | ||
if (branchOffset >= 0x2000000 || branchOffset <= -0x2000000) | if (branchOffset >= 0x2000000 || branchOffset <= -0x2000000) | ||
| Line 418: | Line 449: | ||
} | } | ||
if ( | if (relocType == R_ARM_JUMP24) { | ||
// +-32MB. | // +-32MB. | ||
if (branchOffset >= 0x2000000 || branchOffset <= -0x2000000 || (base & 1)) | if (branchOffset >= 0x2000000 || branchOffset <= -0x2000000 || (base & 1)) | ||
| Line 427: | Line 458: | ||
} | } | ||
if ( | if (relocType == R_ARM_PREL31) { | ||
*out = addend + ((base << 1) >> 1) - inputPtr; | *out = addend + ((base << 1) >> 1) - inputPtr; | ||
return 0; | return 0; | ||
| Line 435: | Line 466: | ||
} | } | ||
== nnroControlObject | == Fields 0x120, 0x130 == | ||
Field 0x130 is a list of relocations which serve an unknown purpose. Field 0x120 is a list of entries related to the previous list: | |||
{| class="wikitable" border="1" | |||
! Offset | |||
! Size | |||
! Description | |||
|- | |||
| 0x0 | |||
| 0x4 | |||
| Offset to a relocation entry in field 0x130 | |||
|- | |||
| 0x4 | |||
| 0x4 | |||
| Segment offset used for calculating the "base" parameter | |||
|} | |||
= nnroControlObject = | |||
Signature: | Signature: | ||
Revision as of 13:08, 26 December 2025
CRO with extension .cro is used for "DLLs". CRS with extension .crs is in the same format of CRO but storing the symbol information of the static module (the main application). The end of the file is aligned to a 0x1000-byte boundary with 0xCC bytes.
The first hash-table entry hashes the 0x100-byte header following the hash-table. The following hash-table entries hash the sections specified in the header.
When the RO module loads the entire CRO into process memory(mapped in the 0x00100000-0x04000000 region), it modifies the mapped CRO data. The magic field is also changed to "FIXD" if fix level is not 0.
Upon loading, the RO module will look for export symbol "nnroAeabiAtexit_" to patch it to its import symbol "__aeabi_atexit".
For dumping symbols and loading a CRO into IDA, see [1] and [2].
Structure
| Offset | Size | Description |
|---|---|---|
| 0x0 | 0x80 | SHA-256 hash-table, verified by CRR |
| 0x80 | 0x04 | Magic "CRO0" |
| 0x84 | 0x04 | Name offset |
| 0x88 | 0x04 | Next loaded CRO pointer, set by RO during loading (Usually zero when the CRO is being loaded) |
| 0x8C | 0x04 | Previous loaded CRO pointer, set by RO during loading |
| 0x90 | 0x04 | File size |
| 0x94 | 0x04 | .bss size |
| 0x98 | 0x04 | Fixed size, set by RO after fixing, used to keep track of the new size |
| 0x9C | 0x04 | Unknown |
| 0xA0 | 0x04 | Segment offset that is always the same as export symbol "nnroControlObject_". 0xFFFFFFFF in CRS |
| 0xA4 | 0x04 | Segment offset for "OnLoad" function, which will be called when the module is initialized. Set to 0xFFFFFFFF if not exists. |
| 0xA8 | 0x04 | Segment offset for "OnExit" function, which will be called when the module is finalized. Set to 0xFFFFFFFF if not exists. |
| 0xAC | 0x04 | Segment offset for "OnUnresolved" function, which will be called when an unresolved function is called. Set to 0xFFFFFFFF if not exists. |
| 0xB0 | 0x04 | Code offset |
| 0xB4 | 0x04 | Code size |
| 0xB8 | 0x04 | .data offset |
| 0xBC | 0x04 | .data size |
| 0xC0 | 0x04 | Module Name offset |
| 0xC4 | 0x04 | Module Name size |
| 0xC8 | 0x04 | Segment Table offset |
| 0xCC | 0x04 | Segment Table num (size = num * 12) |
| 0xD0 | 0x04 | Named Export Table offset |
| 0xD4 | 0x04 | Named Export Table num (size = num * 8) |
| 0xD8 | 0x04 | Indexed Export Table offset |
| 0xDC | 0x04 | Indexed Export Table num (size = num * 4) |
| 0xE0 | 0x04 | Export Strings offset |
| 0xE4 | 0x04 | Export Strings size |
| 0xE8 | 0x04 | Export Trie offset |
| 0xEC | 0x04 | Export Trie num (size = num * 8) |
| 0xF0 | 0x04 | Import Module Table offset |
| 0xF4 | 0x04 | Import Module Table num (size = num * 20) |
| 0xF8 | 0x04 | Import Relocations offset |
| 0xFC | 0x04 | Import Relocations num (size = num * 12) |
| 0x100 | 0x04 | Named Import Table offset |
| 0x104 | 0x04 | Named Import Table num (size = num * 8) |
| 0x108 | 0x04 | Indexed Import Table offset |
| 0x10C | 0x04 | Indexed Import Table num (size = num * 8) |
| 0x110 | 0x04 | Anonymous Import Table offset |
| 0x114 | 0x04 | Anonymous Import Table num (size = num * 8) |
| 0x118 | 0x04 | Import Strings offset |
| 0x11C | 0x04 | Import Strings size |
| 0x120 | 0x04 | Unknown Relocations base parameter offset |
| 0x124 | 0x04 | Unknown Relocations base parameter num (size = num * 8) |
| 0x128 | 0x04 | Internal Relocations offset |
| 0x12C | 0x04 | Internal Relocations num (size = num * 12) |
| 0x130 | 0x04 | Unknown Relocations offset |
| 0x134 | 0x04 | Unknown Relocations num (size = num * 12) |
Segment offset
| Bits | Description |
|---|---|
| 0-3 | Segment index for table |
| 4-31 | Offset into segment |
Segment Table entry
| Offset | Size | Description |
|---|---|---|
| 0x0 | 0x4 | Segment offset |
| 0x4 | 0x4 | Segment size |
| 0x8 | 0x4 | Segment id (0 = .text, 1 = .rodata, 2 = .data, 3 = .bss) |
Named Export Table entry
| Offset | Size | Description |
|---|---|---|
| 0x0 | 0x4 | Name offset |
| 0x4 | 0x4 | Segment offset for export |
Indexed Export Table entry
| Offset | Size | Description |
|---|---|---|
| 0x0 | 0x4 | Segment offset for export |
Export Trie entry
Each entry represents a node in the trie:
| Offset | Size | Description |
|---|---|---|
| 0x0 | 0x2 | Flags (bits 0-2: bit index, bits 3-15: char index) |
| 0x2 | 0x2 | Left node info (bit 0-14: node index, bit 15: is leaf) |
| 0x4 | 0x2 | Right node info (same as left node) |
| 0x6 | 0x2 | Index for the named exports table |
The root node is a dummy node, with the right child being a leaf and pointing to the empty string. Hence the search must be performed starting from the left child.
Search is performed by extracting the bit of the input using informations encoded in the flags. If the bit is 0 or the offsets are out of bounds the search continues on the left, else on the right. The search always yields a result, which may not match the input string, so a final strcmp call is required.
Named Import Table entry
| Offset | Size | Description |
|---|---|---|
| 0x0 | 0x4 | Name offset |
| 0x4 | 0x4 | Offset of the head of a linear list that contains the patches for this import |
Indexed Import Table entry
| Offset | Size | Description |
|---|---|---|
| 0x0 | 0x4 | index of the export symbol |
| 0x4 | 0x4 | Offset of the head of a linear list that contains the patches for this import |
Anonymous Import Table entry
| Offset | Size | Description |
|---|---|---|
| 0x0 | 0x4 | Segment offset of the export symbol |
| 0x4 | 0x4 | Offset of the head of a linear list that contains the patches for this import |
Import Module Table entry
| Offset | Size | Description |
|---|---|---|
| 0x0 | 0x4 | Module name offset |
| 0x4 | 0x4 | Indexed import num |
| 0x8 | 0x4 | Offset of the head of a sub list in Indexed Import Table |
| 0xC | 0x4 | Anonymous import num |
| 0x10 | 0x4 | Offset of the head of a sub list in Anonymous Import Table |
Relocation entry
| Offset | Size | Description |
|---|---|---|
| 0x0 | 0x4 | Segment offset for output. |
| 0x4 | 0x1 | Relocation type (R_ARM_NONE = 0, R_ARM_ABS32 = 2, R_ARM_REL32 = 3, R_ARM_THM_PC22 = 10, R_ARM_CALL = 28, R_ARM_JUMP24 = 29, R_ARM_TARGET1 = 38, R_ARM_PREL31 = 42) |
| 0x5 | 0x1 | For import relocations, non-zero if last entry; for internal relocations, this is the referred segment index |
| 0x6 | 0x1 | For import relocations, 1 is written to first entry if all symbols loaded successfully; unknown (padding?) for internal relocations |
| 0x7 | 0x1 | Unknown (padding?) |
| 0x8 | 0x4 | addend |
Relocation code from RO:
static Result writeRelocation(u32* out, u32 relocType, u32 addend, u32 base, u32 inputPtr) {
const s32 branchOffset = inputPtr - base;
u32 offset = base + addend - inputPtr;
if (relocType == R_ARM_NONE)
return 0;
if (relocType == R_ARM_ABS32 || relocType == R_ARM_TARGET1) {
*out = base + addend;
return 0;
}
if (relocType == R_ARM_REL32) {
*out = offset;
return 0;
}
if (relocType == R_ARM_THM_PC22) {
// +-4MB.
if (branchOffset >= 0x400000 || branchOffset <= -0x400000)
return 0xD9012C23;
if (base & 1) {
*out = (((offset >> 12) | 0xF000) << 16) | ((offset << 4) >> 5) | 0xF800;
} else {
if (offset & 2)
offset += 2;
*out = ((offset >> 12) | 0xF000) << 16 | ((offset << 4) >> 5) | 0xE800;
}
return 0;
}
if (relocType == R_ARM_CALL) {
// +-32MB.
if (branchOffset >= 0x2000000 || branchOffset <= -0x2000000)
return 0xD9012C23;
if (base & 1) {
*out = ((offset << 23) & 0x1000000) | ((offset << 6) >> 8) | 0xFA000000;
} else {
*out = 0xEB000000 | ((offset << 6) >> 8);
}
return 0;
}
if (relocType == R_ARM_JUMP24) {
// +-32MB.
if (branchOffset >= 0x2000000 || branchOffset <= -0x2000000 || (base & 1))
return 0xD9012C23;
*out = (*out & 0xFF000000) | ((offset << 6) >> 8);
return 0;
}
if (relocType == R_ARM_PREL31) {
*out = addend + ((base << 1) >> 1) - inputPtr;
return 0;
}
return 0xD9012C22;
}
Fields 0x120, 0x130
Field 0x130 is a list of relocations which serve an unknown purpose. Field 0x120 is a list of entries related to the previous list:
| Offset | Size | Description |
|---|---|---|
| 0x0 | 0x4 | Offset to a relocation entry in field 0x130 |
| 0x4 | 0x4 | Segment offset used for calculating the "base" parameter |
nnroControlObject
Signature:
Result nnroControlObject(void* arg, u32 type);
| Type | Description |
|---|---|
| 0 | Write address of "nnroEitNode_" to memory pointed by arg (*(u32*)arg = &nnroEitNode_) |
| 1 | Set global in CRO to arg, where arg is the CRO end (g_CRO_end = (u32)arg) |
| 2 | Write CRO end to memory pointed by arg (*(u32*)arg = g_CRO_end) |
| 3 | Write begin, end of function list (C initializers?) to memory pointed by arg (*(u32*)arg = (u32)listBegin, ((u32*)arg)[1] = (u32)listEnd) |
| 4 | Write begin, end of function list (C++ initializers?) to memory pointed by arg (*(u32*)arg = (u32)listBegin, ((u32*)arg)[1] = (u32)listEnd) |
"EitNode" are nodes of a linked list containing informations about the main executable + loaded modules:
| Index Word | Type | Description |
|---|---|---|
| 0 | EitNode* | Pointer to the previous element of the list |
| 1 | EitNode* | Pointer to the next element of the list |
| 2 | u32 | Module code start, for the main executable this is .text base, for CROs this is the value at CRO+0xB0 |
| 3 | u32 | Module code end (start + size), for the main executable the size is the size of all mapped sections, for CROs it's the value at CRO+0xB4 |
| 4 | u32* | Unknown, seems to be an array of u32, probably holds debugging informations |
| 5 | u32* | This points at the end of the previous array |
| 6 | ElfNode* (?) | Unknown, this is only set for the main executable node, and it points to itself |