CRO0: Difference between revisions

Subv (talk | contribs)
No edit summary
Kynex7510 (talk | contribs)
mNo edit summary
 
(12 intermediate revisions by 6 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 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 [https://github.com/plutooo/ctr/] and [https://github.com/wwylele/IDA_plugin_CRO].
= Structure =


{| class="wikitable" border="1"
{| class="wikitable" border="1"
Line 19: Line 31:
|-
|-
| 0x88
| 0x88
| 0x08
| 0x04
| Previous / Next loaded CRO pointer, set by RO during loading
| 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
| 0x90
Line 27: Line 43:
|-
|-
| 0x94
| 0x94
| 0x18
| 0x04
| .bss size
|-
| 0x98
| 0x04
| Unknown
| Unknown
|-
| 0x9C
| 0x04
| Unknown
|-
| 0xA0
| 0x04
| [[#Segment offset (4 bytes)|Segment offset]] that is always the same as export symbol "nnroControlObject_". 0xFFFFFFFF in CRS
|-
| 0xA4
| 0x04
| [[#Segment offset (4 bytes)|Segment offset]] for "OnLoad" function, which will be called when the module is initialized. Set to 0xFFFFFFFF if not exists.
|-
| 0xA8
| 0x04
| [[#Segment offset (4 bytes)|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", symbols are loaded relative to this. Probably filled by dynamic linker, set to 0xFFFFFFFF in file.
| [[#Segment offset (4 bytes)|Segment offset]] for "OnUnresolved" function, which will be called when an unresolved function is called. Set to 0xFFFFFFFF if not exists.
|-
|-
| 0xB0
| 0xB0
Line 44: Line 80:
| 0xB8
| 0xB8
| 0x04
| 0x04
| unk1 offset
| .data offset
|-
|-
| 0xBC
| 0xBC
| 0x04
| 0x04
| unk1 size
| .data size
|-
|-
| 0xC0
| 0xC0
Line 68: Line 104:
| 0xD0
| 0xD0
| 0x04
| 0x04
| Export Table offset
| Named Export Table offset
|-
|-
| 0xD4
| 0xD4
| 0x04
| 0x04
| Export Table num (size = num * 8)
| Named Export Table num (size = num * 8)
|-
|-
| 0xD8
| 0xD8
| 0x04
| 0x04
| unk3 offset
| Indexed Export Table offset
|-
|-
| 0xDC
| 0xDC
| 0x04
| 0x04
| unk3 num
| Indexed Export Table num (size = num * 4)
|-
|-
| 0xE0
| 0xE0
Line 92: Line 128:
| 0xE8
| 0xE8
| 0x04
| 0x04
| Export Tree offset (fast lookups based on strlen)
| Export Tree offset (fast lookups based on a trie-like structure)
|-
|-
| 0xEC
| 0xEC
Line 100: Line 136:
| 0xF0
| 0xF0
| 0x04
| 0x04
| unk4 offset
| Import Module Table offset
|-
|-
| 0xF4
| 0xF4
| 0x04
| 0x04
| unk4 num
| Import Module Table num (size = num * 20)
|-
|-
| 0xF8
| 0xF8
Line 116: Line 152:
| 0x100
| 0x100
| 0x04
| 0x04
| Import Table 1 offset
| Named Import Table offset
|-
|-
| 0x104
| 0x104
| 0x04
| 0x04
| Import Table 1 num (size = num * 8)
| Named Import Table num (size = num * 8)
|-
|-
| 0x108
| 0x108
| 0x04
| 0x04
| Import Table 2 offset
| Indexed Import Table offset
|-
|-
| 0x10C
| 0x10C
| 0x04
| 0x04
| Import Table 2 num (size = num * 8)
| Indexed Import Table num (size = num * 8)
|-
|-
| 0x110
| 0x110
| 0x04
| 0x04
| Import Table 3 offset
| Anonymous Import Table offset
|-
|-
| 0x114
| 0x114
| 0x04
| 0x04
| Import Table 3 num (size = num * 8)
| Anonymous Import Table num (size = num * 8)
|-
|-
| 0x118
| 0x118
Line 171: Line 207:
|}
|}


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


{| class="wikitable" border="1"
{| class="wikitable" border="1"
Line 184: Line 220:
|}
|}


Segment Table entry (12 bytes)
==Segment Table entry (12 bytes)==
{| class="wikitable" border="1"
{| class="wikitable" border="1"
!  Offset
!  Offset
Line 200: Line 236:
| 0x8
| 0x8
| 0x4
| 0x4
| Segment id (0, 1, 2..)
| Segment id (0 = .text, 1 = .rodata, 2 = .data, 3 = .bss)
|}
|}


Export Table entry (8 bytes)
==Named Export Table entry (8 bytes)==
{| class="wikitable" border="1"
{| class="wikitable" border="1"
!  Offset
!  Offset
Line 215: Line 251:
| 0x4
| 0x4
| 0x4
| 0x4
| "Segment offset" for export
| [[#Segment offset (4 bytes)|Segment offset]] for export
|}
|}


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


Patch entry (12 bytes)
==Indexed Import Table entry (8 bytes)==
{| class="wikitable" border="1"
!  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 (8 bytes)==
{| class="wikitable" border="1"
{| class="wikitable" border="1"
!  Offset
!  Offset
Line 241: Line 303:
| 0x0
| 0x0
| 0x4
| 0x4
| "Segment offset" for output.
| [[#Segment offset (4 bytes)|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 (20 bytes)==
{| class="wikitable" border="1"
!  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
|}
 
==Patch entry (12 bytes)==
{| class="wikitable" border="1"
!  Offset
!  Size
!  Description
|-
| 0x0
| 0x4
| [[#Segment offset (4 bytes)|Segment offset]] for output.
|-
|-
| 0x4
| 0x4
| 0x1
| 0x1
| Patch type (0=nothing/ignore, 2=38=write u32 absolute (base+X), 3=write u32 relative (base+X-in_ptr), 10=THUMB branch, 28=ARM32 branch, 29=modify ARM32 branch offset, 42=write u32 relative (((signed int)base*2)/2+X-in_ptr), otherwise err)
| Patch 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
| Non-zero if last entry.
| For import patches, non-zero if last entry; for relocation patches, this is the referred segment index
|-
|-
| 0x6
| 0x6
| 0x1
| 0x1
| 1 is written to last entry if all symbols loaded successfully.
| For import patches, 1 is written to first entry if all symbols loaded successfully; unknown (padding?) for relocation patches
|-
|-
| 0x7
| 0x7
| 0x1
| 0x1
| Unknown
| Unknown (padding?)
|-
|-
| 0x8
| 0x8
| 0x4
| 0x4
| X (00's in file, probably set by dynamic linker)
| addend
|}
|}


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


----
static Result writePatch(u32* out, u32 patchType, u32 addend, u32 base, u32 inputPtr) {
    const s32 branchOffset = inputPtr - base;
    u32 offset = base + addend - inputPtr;
    if (patchType == R_ARM_NONE)
        return 0;
    if (patchType == R_ARM_ABS32 || patchType == R_ARM_TARGET1) {
        *out = base + addend;
        return 0;
    }
    if (patchType == R_ARM_REL32) {
        *out = offset;
        return 0;
    }
    if (patchType == 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 (patchType == 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 (patchType == R_ARM_JUMP24) {
        // +-32MB.
        if (branchOffset >= 0x2000000 || branchOffset <= -0x2000000 || (base & 1))
            return 0xD9012C23;
        *out = (*out & 0xFF000000) | ((offset << 6) >> 8);
        return 0;
    }
    if (patchType == 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 can be used for storing "DLL" symbols as well. The end of the file is aligned to a 0x1000-byte boundary with 0xCC bytes.
== nnroControlObject ==
CRO0 files are usually stored under "romfs:/cro/".


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.
Signature:
 
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".
{| 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 symbol "__aeabi_atexit" or "nnroAeabiAtexit_".
"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/].
{| 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
|}