CRO0: Difference between revisions

added new header info
Import Module Table entry: The order of "Offset" vs "Count" was swapped... at least for this `.crs` file I'm reading.
 
(9 intermediate revisions by 3 users not shown)
Line 1: Line 1:
[[Category:File formats]]
[[Category:File formats]]
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 CRO hashes before the magic are hashed themselves and verified against the [[CRR0|CRR]] hash table.
When the RO module loads the entire CRO into process memory (mapped in the 0x00100000-0x04000000 region), it relocates the mapped CRO data (eg. such that offsets can be dereferenced). 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 [https://github.com/plutooo/ctr/] and [https://github.com/wwylele/IDA_plugin_CRO].
= Structure =


{| class="wikitable" border="1"
{| class="wikitable" border="1"
Line 6: Line 18:
!  Description
!  Description
|-
|-
| 0x0
| 0x00
| 0x80
| 0x20
| SHA-256 hash-table, verified by [[CRR0|CRR]]
| SHA-256 hash over the raw data from offset 0x80 to *(code offset), exclusive
|-
| 0x20
| 0x20
| SHA-256 hash over the raw data from *(code offset) to *(module name offset), exclusive
|-
| 0x40
| 0x20
| SHA-256 hash over the raw data from *(module name offset) to *(data offset), exclusive
|-
| 0x60
| 0x20
| SHA-256 hash over the raw data from *(data offset) to *(data offset + data size), exclusive (RO does NOT check this hash)
|-
|-
| 0x80
| 0x80
Line 36: Line 60:
| 0x98
| 0x98
| 0x04
| 0x04
| Unknown
| Fixed size, set by RO after fixing, used to keep track of the new size
|-
|-
| 0x9C
| 0x9C
Line 44: Line 68:
| 0xA0
| 0xA0
| 0x04
| 0x04
| "Segment offset" that is always the same as export symbol "nnroControlObject_". 0xFFFFFFFF in CRS
| [[#Segment offset|Segment offset]] that is always the same as export symbol "nnroControlObject_". 0xFFFFFFFF in CRS
|-
|-
| 0xA4
| 0xA4
| 0x04
| 0x04
| "Segment offset" for "OnLoad" function, which will be called when the module is initialized. Set to 0xFFFFFFFF if not exists.
| [[#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" for "OnExit" function, which will be called when the module is finalized. Set to 0xFFFFFFFF if not exists.
| [[#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" for "OnUnresolved" function, which will be called when an unresolved function is called. Set to 0xFFFFFFFF if not exists.
| [[#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 88: Line 112:
| 0xCC
| 0xCC
| 0x04
| 0x04
| Segment Table num (size = num*12)
| Segment Table num (size = num * 12)
|-
|-
| 0xD0
| 0xD0
Line 116: Line 140:
| 0xE8
| 0xE8
| 0x04
| 0x04
| Export Tree offset (fast lookups based on a trie-like structure)
| Export Trie offset
|-
|-
| 0xEC
| 0xEC
| 0x04
| 0x04
| Export Tree num (size = num * 8)
| Export Trie num (size = num * 8)
|-
|-
| 0xF0
| 0xF0
Line 132: Line 156:
| 0xF8
| 0xF8
| 0x04
| 0x04
| Import Patches offset
| Import Relocations offset
|-
|-
| 0xFC
| 0xFC
| 0x04
| 0x04
| Import Patches num (size = num * 12)
| Import Relocations num (size = num * 12)
|-
|-
| 0x100
| 0x100
Line 172: Line 196:
| 0x120
| 0x120
| 0x04
| 0x04
| unk8 offset
| [[#Fields 0x120, 0x130|Unknown Relocations]] base parameter offset
|-
|-
| 0x124
| 0x124
| 0x04
| 0x04
| unk8 num
| [[#Fields 0x120, 0x130|Unknown Relocations]] base parameter num (size = num * 8)
|-
|-
| 0x128
| 0x128
| 0x04
| 0x04
| Relocation Patches offset
| Internal Relocations offset
|-
|-
| 0x12C
| 0x12C
| 0x04
| 0x04
| Relocation Patches num (size = num * 12)
| Internal Relocations num (size = num * 12)
|-
|-
| 0x130
| 0x130
| 0x04
| 0x04
| unk9 offset
| [[#Fields 0x120, 0x130|Unknown Relocations]] offset
|-
|-
| 0x134
| 0x134
| 0x04
| 0x04
| unk9 num
| [[#Fields 0x120, 0x130|Unknown Relocations]] num (size = num * 12)
|}
|}


Segment offset (4 bytes)
== Segment offset ==


{| class="wikitable" border="1"
{| class="wikitable" border="1"
Line 208: Line 232:
|}
|}


Segment Table entry (12 bytes)
== Segment Table entry ==
{| class="wikitable" border="1"
{| class="wikitable" border="1"
!  Offset
!  Offset
Line 227: Line 251:
|}
|}


Named Export Table entry (8 bytes)
== Named Export Table entry ==
{| class="wikitable" border="1"
{| class="wikitable" border="1"
!  Offset
!  Offset
Line 239: Line 263:
| 0x4
| 0x4
| 0x4
| 0x4
| "Segment offset" for export
| [[#Segment offset (4 bytes)|Segment offset]] for export
|}
 
== Indexed Export Table entry ==
{| class="wikitable" border="1"
!  Offset
!  Size
!  Description
|-
| 0x0
| 0x4
| [[#Segment offset (4 bytes)|Segment offset]] for export
|}
|}


Indexed Export Table entry (4 bytes)
== Export Trie entry ==
 
Each entry represents a node in the trie:
 
{| class="wikitable" border="1"
{| class="wikitable" border="1"
!  Offset
!  Offset
Line 249: Line 287:
|-
|-
| 0x0
| 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
| 0x4
| "Segment offset" for export
| 0x2
| Right node info (same as left node)
|-
| 0x6
| 0x2
| Index for the named exports table
|}
|}


Named Import Table entry (8 bytes)
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 268: Line 323:
|}
|}


Indexed Import Table entry (8 bytes)
== Indexed Import Table entry ==
{| class="wikitable" border="1"
{| class="wikitable" border="1"
!  Offset
!  Offset
Line 283: Line 338:
|}
|}


Anonymous Import Table entry (8 bytes)
== Anonymous Import Table entry ==
{| class="wikitable" border="1"
{| class="wikitable" border="1"
!  Offset
!  Offset
Line 291: Line 346:
| 0x0
| 0x0
| 0x4
| 0x4
| "Segment offset" of the export symbol
| [[#Segment offset (4 bytes)|Segment offset]] of the export symbol
|-
|-
| 0x4
| 0x4
Line 298: Line 353:
|}
|}


Import Module Table entry (20 bytes)
== Import Module Table entry ==
{| class="wikitable" border="1"
{| class="wikitable" border="1"
!  Offset
!  Offset
Line 310: Line 365:
| 0x4
| 0x4
| 0x4
| 0x4
| Indexed import num
| Offset of the head of a sub list in Indexed Import Table
|-
|-
| 0x8
| 0x8
| 0x4
| 0x4
| Offset of the head of a sub list in Indexed Import Table
| Indexed import num
|-
|-
| 0xC
| 0xC
| 0x4
| 0x4
| Anonymous import num
| Offset of the head of a sub list in Anonymous Import Table
|-
|-
| 0x10
| 0x10
| 0x4
| 0x4
| Offset of the head of a sub list in Anonymous Import Table
| Anonymous import num
|}
|}


Patch entry (12 bytes)
== Relocation entry ==
{| class="wikitable" border="1"
{| class="wikitable" border="1"
!  Offset
!  Offset
Line 333: Line 388:
| 0x0
| 0x0
| 0x4
| 0x4
| "Segment offset" for output.
| [[#Segment offset (4 bytes)|Segment offset]] for output.
|-
|-
| 0x4
| 0x4
| 0x1
| 0x1
| Patch type (0=nothing/ignore, 2=38=write u32 absolute (base+addend), 3=write u32 relative (base+addend-in_ptr), 10=THUMB branch, 28=ARM32 branch, 29=modify ARM32 branch offset, 42=write u32 relative (((signed int)base*2)/2+addend-in_ptr), otherwise err) (This is apparently a subset of relocation type for ARM ELF)
| 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 patches, non-zero if last entry; for relocation patches, this is the referred segment index
| For import relocations, non-zero if last entry; for internal relocations, this is the referred segment index
|-
|-
| 0x6
| 0x6
| 0x1
| 0x1
| For import patches, 1 is written to first entry if all symbols loaded successfully; unknown (padding?) for relocation patches
| For import relocations, 1 is written to first entry if all symbols loaded successfully; unknown (padding?) for internal relocations
|-
|-
| 0x7
| 0x7
Line 356: Line 411:
|}
|}


ARM32 branch instruction is constructed as follows:
Relocation code from RO:
  If addend > 0x2000000 or addend < 0xFE000000, then skip.
  If (addend&1) == 1 then write "b +4" (nop).
  Else write as normal.


----
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;
}


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.
== 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:


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.
Result nnroControlObject(void* arg, u32 type);


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.
{| class="wikitable" border="1"
!  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)
|}


Upon loading, the RO module will look for export symbol "nnroAeabiAtexit_" to patch it to its import symbol "__aeabi_atexit".
"EitNode" are nodes of a linked list containing informations about the main executable + loaded modules:


For dumping symbols and loading a CRO into IDA, see [https://github.com/plutooo/ctr/] and [https://github.com/wwylele/IDA_plugin_CRO].
{| class="wikitable" border="1"
!  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
|}