Difference between revisions of "RetailOS"

From freemyipod.org
Jump to: navigation, search
m (add a RTXC 3.2 training manual I found on archive.org)
 
(16 intermediate revisions by 2 users not shown)
Line 3: Line 3:
 
== Naming ==
 
== Naming ==
  
The only 'official' name seems to be 'RetailOS', found in the [[Nano 3G]] WTF. It is also referred to as 'osos' per the file name in the resource partition of the firmware bundle.
+
The only 'official' name seems to be 'retailOS', found in the [[Nano 3G]] WTF. It is also referred to as 'osos' per the file name in the resource partition of the firmware bundle.
  
 
== Architecture ==
 
== Architecture ==
  
OSOS is a small, embedded, single-user, single-binary, real time operating system. With time it acquire more and more complex functionality, like PowerVR drivers and being able to load external applications ('eApps') which are used for games.
+
retailOS is a small, embedded, single-user, single-binary, real time operating system. With time it acquire more and more complex functionality, like PowerVR drivers and being able to load external applications ('eApps') which are used for games.
  
The core of the system is based on RTXC 3.2, with the end-user interface based on intellectual property from a company called Pixo. <ref>https://twitter.com/johnwhitley/status/1451952369248264201</ref>
+
The core of the system is based on RTXC 3.2, with the end-user interface based on intellectual property from a company called Pixo. <ref>https://web.archive.org/web/20230224105131/https://twitter.com/johnwhitley/status/1451952369248264201</ref>
  
 
== Security ==
 
== Security ==
  
As evidenced by the success of the [[Notes vulnerability]], at least up to Nano 4G there was no kind of security hardening, and in fact all processes, including games, seem to be running in ARM system mode. This should make exploitation of newer OSOS bugs trivial.
+
As evidenced by the success of the [[Notes vulnerability]], at least up to Nano 4G there was no kind of security hardening, and in fact all processes, including games, seem to be running in ARM system mode. This should make exploitation of newer retailOS bugs trivial.
  
 
=== Boot chain ===
 
=== Boot chain ===
  
OSOS is loaded by the second-stage bootloader (stored on NOR/NAND depending on the device generation), from NAND into DRAM.
+
retailOS is loaded by the second-stage bootloader (stored on NOR/NAND depending on the device generation), from NAND into DRAM.
  
While other stages of the boot chain (eg. the bootloader, WTF mode, the diagnostics tool) are based around EFI firmware volumes and an EFI runtime, OSOS is a single binary blob without any built-in modularity.
+
While other stages of the boot chain (eg. the bootloader, WTF mode in newer devices, the diagnostics tool) are based around EFI firmware volumes and an EFI runtime, retailOS is a single binary blob without any built-in modularity.
  
 
=== eApp Signing ===
 
=== eApp Signing ===
Line 27: Line 27:
 
== Options ==
 
== Options ==
  
We have found some 'secret' options that can be set by creating specially named files. See [[OSOS_Options|Options]].
+
We have found some 'secret' options that can be set by creating specially named files. See [[RetailOS_Options|Options]].
 +
 
 +
== Analysis / Memory Layout ==
 +
 
 +
Loading RetailOS correctly into a decompiler/disassembler is tricky, as the contents of the IMG1 image are a binary blob which self-relocates to the correct places in memory.
 +
 
 +
These are the memory segments within RetailOS that we know of (at least on Nano 5G):
 +
 
 +
{| class="wikitable"
 +
|-
 +
! Name !! Marker !! Location in memory !! Description
 +
|-
 +
| sram.text || n/a || SRAM 0x22000000 || SRAM-resident code, most of RTXC lives here.
 +
|-
 +
| sram.bss || n/a || SRAM 0x22030000 || SRAM-resident zero data.
 +
|-
 +
| sram.data || n/a || SRAM 0x22030000 + sram_bss_size || SRAM-resident data.
 +
|-
 +
| dram.textdata || hibe || DRAM 0x08000000 || Combined .text and .data which lives in DRAM. Bulk of code lives here.
 +
|-
 +
| dram.frameworks || miscTBD || DRAM 0x08000000 + dram_textdata_size || 'Framework' system of some kind, interfaces used by eApps.
 +
|-
 +
| dram.bss || n/a || DRAM 0x08000000 + dram_textdata_size + dram_frameworks_size || DRAM-resident zero data.
 +
|}
 +
 
 +
And here's how the segments are built up within the RetailOS binary blob:
 +
 
 +
{| class="wikitable"
 +
|-
 +
! Address !! Name !! Size
 +
|-
 +
| Start || sram.text || sram_text_size
 +
|-
 +
| || sram.bss || sram_bss_size
 +
|-
 +
| || sram.data || sram_data_size
 +
|-
 +
| || dram.text || dram_text_size
 +
|-
 +
| End || dram.frameworks || dram_frameworks_size
 +
|}
 +
 
 +
(yes, the firmware blob ships a sram.bss physically in the file)
 +
 
 +
So the goal to be able to load the binary is to figure out the segment sizes and then load them into a decompiler/disassembler.
 +
 
 +
Here, we'll show how to figure out the segment sizes for N5G. First, load the RetailOS body (without the header!) at 0x22000000 in a decompiler. We load it there (intead of into DRAM as it is done on the device) as the stub relocates to this address first by performing the SRAM .text/.data copies very early in the process, and the code is position independent for only a short time.
 +
 
 +
Then, look at the start function (follow the reset vector):
 +
 
 +
<pre>
 +
void start(void) { // 0x2200505c
 +
    offs = relocation_offset();
 +
    /* ... peeks/pokes to bus matrix periph at 0x3ff00000 ... */
 +
    if (offs != 0) {
 +
        relocate(offs);
 +
    }
 +
    (*0x22000000) = 0xea000007;
 +
    zero_bss();
 +
}
 +
</pre>
 +
 
 +
relocation_offset will return 0 if the stub is already at 0x22000000, so will return 0 for the way we've loaded it. On a real device, this will be 0x22000000 - 0x08000000 ==
 +
0x1a000000, as the real device loads RetailOS into DRAM first. Thus, relocate() will be called:
 +
 
 +
<pre>
 +
void relocate(int offs) { // 0x22005ec8
 +
  int iVar1 = -offs;
 +
  void *blob_start = iVar1 + 0x22000000;
 +
  memmove(0x22000000, blob_start, 0xe27c); // copy sram.text
 +
  memzero(0x22000000 + 0xe27c, 0xbc4); // zero out sram.bss within blob
 +
  memmove(0x22030000, 0x22000000 + 0xe27c + iVar1, 0x20000); // copy sram.bss + sram.data
 +
  jump_offset(offs);
 +
  memmove(0x08000000, 0x22000000 + 0xe27c + 0x20000 + iVar1, 0x6c3768); // copy dram.textdata
 +
  memmove(0x08000000 + 0x6c3768, iVar1 + 0x22000000 + 0xe27c + 0x20000 + 0x6c3768), 0xc40); // copy dram.frameworks
 +
  start();
 +
  return;
 +
}
 +
</pre>
 +
 
 +
The above listing shows reconstituted address calculations - in a plain decompilation, all the additions will of course be simplified to a single constant. But you should be able to figure out the following:
 +
 
 +
# sram_text_size is 0xe27c
 +
# sram_bss_size is 0xbc4
 +
# sram_bss_size + sram_data_size is 0x20000
 +
# dram_textdata_size is 0x6c3768
 +
# dram_frameworks_size is 0xc40
 +
 
 +
Then, in zero_bss we can find the size of dram.bss:
 +
 
 +
<pre>
 +
void zero_bss(void) { // 0x22005fec
 +
    memzero(0x2200e27c, 0xbc4); // zero out sram.bss
 +
    // inlined memzero:
 +
    void *start = 0x08000000 + 0x6c3768 + 0xc40;
 +
    int size = 0x790a84;
 +
    // ...
 +
}
 +
</pre>
 +
 
 +
From which we can figure out that the dram.bss segment size is 0x790a84.
 +
 
 +
Thus we can load the file like so (combining sram.bss and sram.data) into a 'clean' decompiler/disassembler session:
 +
 
 +
{| class="wikitable"
 +
|-
 +
! Name !! Memory Address !! File Offset
 +
|-
 +
| sram.text || 0x22000000 || 0x00000000
 +
|-
 +
| sram.bssdata || 0x22030000 || 0x0000e27c
 +
|-
 +
| dram.textdata || 0x08000000 || 0x0002e27c (0xe27c + 0x20000)
 +
|-
 +
| dram.frameworks || 0x086c3768 || 0x006f19e4 (0xe27c + 0x20000 + 0x6c3768)
 +
|-
 +
| dram.bss || 0x086c43a || n/a (0x790a84 zeroes)
 +
|}
 +
 
 +
Writing an automated converter into ELF from arbitrary RetailOS blobs is an exercise left to the reader.
 +
 
 +
== RTXC ==
 +
 
 +
=== Documentation ===
 +
 
 +
This seems to be the best public document available about RTXC 3.2: [https://web.archive.org/web/20230218212424/https://datasheet.datasheetarchive.com/originals/library/Datasheets-AS2/DSAAXSA0003458.pdf DSAAXSA0003458.pdf]. It contains example code for most services, but unfortunately is still missing any structure definitions.
 +
 
 +
There's also some training slides available: [https://ia801800.us.archive.org/26/items/manualzilla-id-5752851/5752851.pdf 5752851.pdf]. These introduce the general architecture and concept of RTXC 3.2.
 +
 
 +
=== Services / Syscalls ===
 +
 
 +
While RTXC documentation speaks mostly of 'kernel services' (which are defined as C function signatures/symbols), we like to talk about 'syscalls' and 'syscall numbers' when reverse engineering retailOS. All service functions go through a central dispatch function and that's the easiest point to start reverse engineering the kernel service interface.
 +
 
 +
The dispatcher receives a saved caller state which contains a pointer to a serialized syscall request in its saved R0. The syscall request is a trivial structure containing a syscall number and arguments. The dispatcher is executed with interrupts enabled (and thus is non-preemptable) and performs actual work on kernel structures. There is no privilege-granting 'gate' mechanism, all caller code is just as privileged as the kernel code.
 +
 
 +
Service functions in turn prepare the syscall request structure (including syscall number), and then call an intermediary state saving function which then calls the dispatcher after disabling interrupts. Some syscall numbers are used by multiple service functions, with some extra arguments in the request being used to decide on the behaviour of the service call (eg. blocking/nonblocking).
 +
 
 +
The following table comes from cross-referencing retailOS, publicly available RTXC PDFs and publicly availble RTXC binaries with debug symbols.
 +
 
 +
{| class="wikitable"
 +
|-
 +
! Name !! Number !! Description
 +
|-
 +
| <code>void KS_pend(SEMA sema)</code> || 0x03 || Semaphore DONE -> PENDING.
 +
|-
 +
| <code>RTXCMSG *KS_receive(MBOX mailbox, TASK  task)</code> || 0x05 || Receive from mailbox.
 +
|-
 +
| <code>KSRC KS_enqueue[w](QUEUE queue, void *entry)</code> || 0x0c || Push into FIFO (and block if full with 'w' variant).
 +
|-
 +
| <code>void KS_dequeue[w](QUEUE queue, void *dest)</code> || 0x0d || Pop from FIFO (and block if empty with 'w' variant).
 +
|-
 +
| <code>KSRC KS_lock(RESOURCE resource)</code> || 0x0e || Lock a resource.
 +
|-
 +
| <code>KSRC KS_lockt(RESOURCE resource, TICKS timoeut)</code> || 0x0e || Lock a resource with timeout.
 +
|-
 +
| <code>KSRC KS_unlock(RESOURCE resource)</code> || 0x0f || Unlock an owned resource.
 +
|-
 +
| <code>CLKBLK *KS_alloc_timer(void)</code> || 0x10 || Allocate next free timer from pool.
 +
|-
 +
| <code>CLKBLK *KS_start_timer(CLKBLK *timer, TICKS initial_period, TICKS cycle_time, SEMA sema)</code> || 0x12 || Start timer.
 +
|-
 +
| <code>KSRC KS_stop_timer(CLKBLK *timer)</code> || 0x13 || Stop timer.
 +
|-
 +
| <code>void KS_delay(TASK task, TICKS period)</code> || 0x14 || Block specified task for a period of time.
 +
|-
 +
| <code>void KS_execute(TASK task)</code> || 0x15 || Start a task from its beginning address.
 +
|-
 +
| <code>KSRC KS_deftask(TASK task, PRIORITY priority, char *stack, size_t stacksize, void (*entry)(void))</code> || 0x16 || Define the attributes of an inactive task.
 +
|-
 +
| <code>TASK KS_alloc_task(void)</code> || 0x17 || Allocate the next available Task Control Block from the pool of free TCBs.
 +
|-
 +
| <code>void KS_terminate(TASK task)</code> || 0x18 || Stop a task by setting it to INACTIVE.
 +
|-
 +
| <code>void KS_suspend(TASK task)</code> || 0x19 || Suspend a task until resumed or re-executed.
 +
|-
 +
| <code>void KS_defpriority(TASK task, PRIORITY priority)</code> || 0x1b || Define or set priority of task.
 +
|-
 +
| <code>void KS_yield(void)</code> || 0x1c || Voluntary release of control to any other task of the same priority.
 +
|-
 +
| <code>SEMA KS_waitm(SEMA *semalist)</code> || 0x22 || Wait on multiple semaphores.
 +
|-
 +
| <code>time_t KS_inqtime(void)</code> || 0x24 || Get current time-of-day.
 +
|-
 +
| <code>void KS_deftime(time_t time)</code> || 0x25 || Set current time-of-day.
 +
|-
 +
| <code>TASK KS_inqres(RESOURCE resource)</code> || 0x26 || Get owner of resource.
 +
|-
 +
| <code>KSRC KS_defres(RESOURCE resource, RESATTR condition)</code> || 0x27 || Define priority inversion on resource.
 +
|-
 +
| <code>void *KS_inqtask_arg(TASK task)</code> || 0x28 || Get environment arguments of task.
 +
|-
 +
| <code>void KS_deftask_arg(TASK task, void *arg)</code> || 0x29 || Set environment arguments for task.
 +
|-
 +
| <code>KSRC KS_defqueue(QUEUE queue, size_t width, int depth, void *body, int currsize)</code> || 0x2e || Define queue.
 +
|-
 +
| <code>int KS_user(int (*func) (void *), void *arg)</code> || 0x30 || Execute function as if it were kernel service.
 +
|}
 +
 
 +
The RTXC memory allocation facilities (<code>KS_alloc/free/create_part/alloc_part/defpart/free_part</code>) are ''not'' used by retailOS and not built into the service dispatcher, at least on [[Nano 5G]].
 +
 
 +
=== Semaphores ===
 +
 
 +
The following semaphores are defined in the [[Nano 3G]] retailOS:
 +
 
 +
{| class="wikitable"
 +
|-
 +
! Number !! Name !! Description
 +
|-
 +
| 0x01 || <code>S_FW_PWR_CHANGE</code> ||
 +
|-
 +
| 0x02 || <code>S_BAT_PWR_CHANGE</code> ||
 +
|-
 +
| 0x03 || <code>S_USB_PWR_CHANGE</code> ||
 +
|-
 +
| 0x04 || <code>S_CNA_CHANGE</code> ||
 +
|-
 +
| 0x05 || <code>S_WHEEL_CHANGE</code> ||
 +
|-
 +
| 0x06 || <code>S_DISKMGRQ</code> ||
 +
|-
 +
| 0x07 || <code>S_TOPPLUG_SWITCH</code> ||
 +
|-
 +
| 0x08 || <code>S_RTCTIMERMGR</code> ||
 +
|-
 +
| 0x09 || <code>S_ALARM_01</code> ||
 +
|-
 +
| 0x0a || <code>S_ALARM_02</code> ||
 +
|-
 +
| 0x0b || <code>S_ALARM_03</code> ||
 +
|-
 +
| 0x0c || <code>S_WATCHDOG</code> ||
 +
|-
 +
| 0x0d || <code>S_CPUMGRQ</code> ||
 +
|-
 +
| 0x0e || <code>S_PCFPOWERMGR</code> ||
 +
|-
 +
| 0x0f || <code>S_POWER_STATE_AC</code> ||
 +
|-
 +
| 0x10 || <code>S_CGR_STATE_TMR</code> ||
 +
|-
 +
| 0x11 || <code>S_DEEPSLEEP</code> ||
 +
|-
 +
| 0x12 || <code>S_ALARM_DONE</code> ||
 +
|-
 +
| 0x13 || <code>S_PIEZOMGR</code> ||
 +
|-
 +
| 0x14 || <code>S_PIEZOMGRSNDR</code> ||
 +
|-
 +
| 0x15 || <code>S_PIEZODONE</code> ||
 +
|-
 +
| 0x16 || <code>S_ACCPOWER</code> ||
 +
|-
 +
| 0x17 || <code>S_ACC_REINIT</code> ||
 +
|-
 +
| 0x18 || <code>S_TOPPLUGSENSER</code> ||
 +
|-
 +
| 0x19 || <code>S_TOPPLUGCHANGE</code> ||
 +
|-
 +
| 0x1a || <code>S_BTMCONNECT</code> ||
 +
|-
 +
| 0x1b || <code>S_BTMPLUGCHANGE</code> ||
 +
|-
 +
| 0x1c || <code>S_BTMREVERIFY</code> ||
 +
|-
 +
| 0x1d || <code>S_BTMREVERTIMED</code> ||
 +
|-
 +
| 0x1e || <code>S_BTMVERCOMP</code> ||
 +
|-
 +
| 0x1f || <code>S_TOPACCPKTRCVD</code> ||
 +
|-
 +
| 0x20 || <code>S_BTMACCPKTRCVD</code> ||
 +
|-
 +
| 0x21 || <code>S_SERIALIDRCVD</code> ||
 +
|-
 +
| 0x22 || <code>S_UARTATXEMPTY</code> ||
 +
|-
 +
| 0x23 || <code>S_UARTBTXEMPTY</code> ||
 +
|-
 +
| 0x24 || <code>S_HDDSCANCOMP</code> ||
 +
|-
 +
| 0x25 || <code>S_BL_ON</code> ||
 +
|-
 +
| 0x26 || <code>S_BL_OFF</code> ||
 +
|-
 +
| 0x27 || <code>S_BL_RAMPDOWN</code> ||
 +
|-
 +
| 0x28 || <code>S_BL_RAMPUP</code> ||
 +
|-
 +
| 0x29 || <code>S_BL_TIMESUP</code> ||
 +
|-
 +
| 0x2a || <code>S_BATT_TIMESUP</code> ||
 +
|-
 +
| 0x2b || <code>S_BATT_AC_PWR</code> ||
 +
|-
 +
| 0x2c || <code>S_BATT_TMR_RST</code> ||
 +
|-
 +
| 0x2d || <code>S_GRAPHMGR</code> ||
 +
|-
 +
| 0x2e || <code>S_VBL</code> ||
 +
|-
 +
| 0x2f || <code>S_DTVRECOVERY</code> ||
 +
|-
 +
| 0x30 || <code>S_CM_HEADPHONE</code> ||
 +
|-
 +
| 0x31 || <code>S_CM_EXTPOWER</code> ||
 +
|-
 +
| 0x32 || <code>S_CM_ACCATTACHED</code> ||
 +
|-
 +
| 0x33 || <code>S_CM_DAC_SETUP</code> ||
 +
|-
 +
| 0x34 || <code>S_ATAWRKLPRDY</code> ||
 +
|-
 +
| 0x35 || <code>S_RTXCBUG</code> ||
 +
|-
 +
| 0x36 || <code>S_BLOCKDEVICE</code> ||
 +
|-
 +
| 0x37 || <code>S_BLOCKDEVICEQ</code> ||
 +
|-
 +
| 0x38 || <code>S_DISPLAY</code> ||
 +
|-
 +
| 0x39 || <code>S_ARB_READY</code> ||
 +
|-
 +
| 0x3a || <code>S_I2C_DONE</code> ||
 +
|-
 +
| 0x3b || <code>S_VSYNC</code> ||
 +
|}
 +
 
 +
There are three more semaphores (0x3c, 0x3d, 0x3e) that have no name defined and are likely unused. Anything 0x3f and up is a 'Dynamic' semaphore defined at runtime (which we haven't reversed yet).
 +
 
 +
=== Queues ===
 +
 
 +
The following queues are defined in the [[Nano 3G]] retailOS:
 +
 
 +
{| class="wikitable"
 +
|-
 +
! Number !! Name !! Description
 +
|-
 +
| 0x01 || PIXORESQ ||
 +
|-
 +
| 0x02 || PIXOSEMAQ ||
 +
|-
 +
| 0x03 || POSIXRESQ ||
 +
|-
 +
| 0x04 || POSIXSEMAQ ||
 +
|}
 +
 
 +
=== Mailboxes ===
 +
 
 +
The following mailboxes are defined in the [[Nano 3G]] retailOS:
 +
 
 +
{| class="wikitable"
 +
|-
 +
! Number !! Name !! Description
 +
|-
 +
| 0x01 || M_DISKMGR ||
 +
|-
 +
| 0x02 || M_PIEZOMGR ||
 +
|-
 +
| 0x03 || M_GRAPHMGR ||
 +
|-
 +
| 0x04 || M_BLOCKDEVICE ||
 +
|-
 +
| 0x05 || M_DISPLAY ||
 +
|}
 +
 
 +
=== Resources ===
 +
 
 +
The following lockable resources are defined in the [[Nano 3G]] retailOS:
 +
 
 +
{| class="wikitable"
 +
|-
 +
! Number !! Name !! Description
 +
|-
 +
| 0x01 || GPIO_REG_WRITE ||
 +
|-
 +
| 0x02 || GPIO_INT_INIT ||
 +
|-
 +
| 0x03 || RTC_TIME_ADJUST ||
 +
|-
 +
| 0x04 || RTC_ALARM_ADJUST ||
 +
|-
 +
| 0x05 || I2C_MASTER ||
 +
|-
 +
| 0x06 || USB_GRANT ||
 +
|-
 +
| 0x07 || USB_RESP_INIT ||
 +
|-
 +
| 0x08 || USB_RESPONDER ||
 +
|-
 +
| 0x09 || DISKPWRMGRSEND ||
 +
|-
 +
| 0x0a || PIEZOMGRSEND ||
 +
|-
 +
| 0x0b || SERIALVERIFIER ||
 +
|-
 +
| 0x0c || RESISTORVERIFIER ||
 +
|-
 +
| 0x0d || FW_IRAM ||
 +
|-
 +
| 0x0e || ACCPOWER ||
 +
|-
 +
| 0x0f || UARTA ||
 +
|-
 +
| 0x10 || UARGB ||
 +
|-
 +
| 0x11 || PMU_LOCK ||
 +
|-
 +
| 0x12 || ADC_LOCK ||
 +
|-
 +
| 0x13 || DTV_ENC_INIT ||
 +
|-
 +
| 0x14 || BACKLIGHT ||
 +
|}
  
 
== External links ==
 
== External links ==
  
 
* [https://web.archive.org/web/19990220054659/http://www.rtxc.com/Products/RTXC/Services.htm RTXC Kernel Services (1999)]
 
* [https://web.archive.org/web/19990220054659/http://www.rtxc.com/Products/RTXC/Services.htm RTXC Kernel Services (1999)]
 +
* [https://archive.org/details/manualzilla-id-5752851 RTXC 3.2 Training Manual]

Latest revision as of 21:44, 9 May 2024

The stock operating system running on non-iOS iPods. It runs everything from device drivers to the clickwheel user interface.

Naming

The only 'official' name seems to be 'retailOS', found in the Nano 3G WTF. It is also referred to as 'osos' per the file name in the resource partition of the firmware bundle.

Architecture

retailOS is a small, embedded, single-user, single-binary, real time operating system. With time it acquire more and more complex functionality, like PowerVR drivers and being able to load external applications ('eApps') which are used for games.

The core of the system is based on RTXC 3.2, with the end-user interface based on intellectual property from a company called Pixo. [1]

Security

As evidenced by the success of the Notes vulnerability, at least up to Nano 4G there was no kind of security hardening, and in fact all processes, including games, seem to be running in ARM system mode. This should make exploitation of newer retailOS bugs trivial.

Boot chain

retailOS is loaded by the second-stage bootloader (stored on NOR/NAND depending on the device generation), from NAND into DRAM.

While other stages of the boot chain (eg. the bootloader, WTF mode in newer devices, the diagnostics tool) are based around EFI firmware volumes and an EFI runtime, retailOS is a single binary blob without any built-in modularity.

eApp Signing

Not yet documented fully. Each game seems to ship with a Manifest.plist.p7p which is a PKCS#7 signature for the main Manifest.plist.

Options

We have found some 'secret' options that can be set by creating specially named files. See Options.

Analysis / Memory Layout

Loading RetailOS correctly into a decompiler/disassembler is tricky, as the contents of the IMG1 image are a binary blob which self-relocates to the correct places in memory.

These are the memory segments within RetailOS that we know of (at least on Nano 5G):

Name Marker Location in memory Description
sram.text n/a SRAM 0x22000000 SRAM-resident code, most of RTXC lives here.
sram.bss n/a SRAM 0x22030000 SRAM-resident zero data.
sram.data n/a SRAM 0x22030000 + sram_bss_size SRAM-resident data.
dram.textdata hibe DRAM 0x08000000 Combined .text and .data which lives in DRAM. Bulk of code lives here.
dram.frameworks miscTBD DRAM 0x08000000 + dram_textdata_size 'Framework' system of some kind, interfaces used by eApps.
dram.bss n/a DRAM 0x08000000 + dram_textdata_size + dram_frameworks_size DRAM-resident zero data.

And here's how the segments are built up within the RetailOS binary blob:

Address Name Size
Start sram.text sram_text_size
sram.bss sram_bss_size
sram.data sram_data_size
dram.text dram_text_size
End dram.frameworks dram_frameworks_size

(yes, the firmware blob ships a sram.bss physically in the file)

So the goal to be able to load the binary is to figure out the segment sizes and then load them into a decompiler/disassembler.

Here, we'll show how to figure out the segment sizes for N5G. First, load the RetailOS body (without the header!) at 0x22000000 in a decompiler. We load it there (intead of into DRAM as it is done on the device) as the stub relocates to this address first by performing the SRAM .text/.data copies very early in the process, and the code is position independent for only a short time.

Then, look at the start function (follow the reset vector):

void start(void) { // 0x2200505c
    offs = relocation_offset();
    /* ... peeks/pokes to bus matrix periph at 0x3ff00000 ... */
    if (offs != 0) {
        relocate(offs);
    }
    (*0x22000000) = 0xea000007;
    zero_bss();
}

relocation_offset will return 0 if the stub is already at 0x22000000, so will return 0 for the way we've loaded it. On a real device, this will be 0x22000000 - 0x08000000 == 0x1a000000, as the real device loads RetailOS into DRAM first. Thus, relocate() will be called:

void relocate(int offs) { // 0x22005ec8
  int iVar1 = -offs;
  void *blob_start = iVar1 + 0x22000000;
  memmove(0x22000000, blob_start, 0xe27c); // copy sram.text
  memzero(0x22000000 + 0xe27c, 0xbc4); // zero out sram.bss within blob
  memmove(0x22030000, 0x22000000 + 0xe27c + iVar1, 0x20000); // copy sram.bss + sram.data
  jump_offset(offs);
  memmove(0x08000000, 0x22000000 + 0xe27c + 0x20000 + iVar1, 0x6c3768); // copy dram.textdata
  memmove(0x08000000 + 0x6c3768, iVar1 + 0x22000000 + 0xe27c + 0x20000 + 0x6c3768), 0xc40); // copy dram.frameworks
  start();
  return;
}

The above listing shows reconstituted address calculations - in a plain decompilation, all the additions will of course be simplified to a single constant. But you should be able to figure out the following:

  1. sram_text_size is 0xe27c
  2. sram_bss_size is 0xbc4
  3. sram_bss_size + sram_data_size is 0x20000
  4. dram_textdata_size is 0x6c3768
  5. dram_frameworks_size is 0xc40

Then, in zero_bss we can find the size of dram.bss:

void zero_bss(void) { // 0x22005fec
    memzero(0x2200e27c, 0xbc4); // zero out sram.bss
    // inlined memzero:
    void *start = 0x08000000 + 0x6c3768 + 0xc40;
    int size = 0x790a84;
    // ...
}

From which we can figure out that the dram.bss segment size is 0x790a84.

Thus we can load the file like so (combining sram.bss and sram.data) into a 'clean' decompiler/disassembler session:

Name Memory Address File Offset
sram.text 0x22000000 0x00000000
sram.bssdata 0x22030000 0x0000e27c
dram.textdata 0x08000000 0x0002e27c (0xe27c + 0x20000)
dram.frameworks 0x086c3768 0x006f19e4 (0xe27c + 0x20000 + 0x6c3768)
dram.bss 0x086c43a n/a (0x790a84 zeroes)

Writing an automated converter into ELF from arbitrary RetailOS blobs is an exercise left to the reader.

RTXC

Documentation

This seems to be the best public document available about RTXC 3.2: DSAAXSA0003458.pdf. It contains example code for most services, but unfortunately is still missing any structure definitions.

There's also some training slides available: 5752851.pdf. These introduce the general architecture and concept of RTXC 3.2.

Services / Syscalls

While RTXC documentation speaks mostly of 'kernel services' (which are defined as C function signatures/symbols), we like to talk about 'syscalls' and 'syscall numbers' when reverse engineering retailOS. All service functions go through a central dispatch function and that's the easiest point to start reverse engineering the kernel service interface.

The dispatcher receives a saved caller state which contains a pointer to a serialized syscall request in its saved R0. The syscall request is a trivial structure containing a syscall number and arguments. The dispatcher is executed with interrupts enabled (and thus is non-preemptable) and performs actual work on kernel structures. There is no privilege-granting 'gate' mechanism, all caller code is just as privileged as the kernel code.

Service functions in turn prepare the syscall request structure (including syscall number), and then call an intermediary state saving function which then calls the dispatcher after disabling interrupts. Some syscall numbers are used by multiple service functions, with some extra arguments in the request being used to decide on the behaviour of the service call (eg. blocking/nonblocking).

The following table comes from cross-referencing retailOS, publicly available RTXC PDFs and publicly availble RTXC binaries with debug symbols.

Name Number Description
void KS_pend(SEMA sema) 0x03 Semaphore DONE -> PENDING.
RTXCMSG *KS_receive(MBOX mailbox, TASK task) 0x05 Receive from mailbox.
KSRC KS_enqueue[w](QUEUE queue, void *entry) 0x0c Push into FIFO (and block if full with 'w' variant).
void KS_dequeue[w](QUEUE queue, void *dest) 0x0d Pop from FIFO (and block if empty with 'w' variant).
KSRC KS_lock(RESOURCE resource) 0x0e Lock a resource.
KSRC KS_lockt(RESOURCE resource, TICKS timoeut) 0x0e Lock a resource with timeout.
KSRC KS_unlock(RESOURCE resource) 0x0f Unlock an owned resource.
CLKBLK *KS_alloc_timer(void) 0x10 Allocate next free timer from pool.
CLKBLK *KS_start_timer(CLKBLK *timer, TICKS initial_period, TICKS cycle_time, SEMA sema) 0x12 Start timer.
KSRC KS_stop_timer(CLKBLK *timer) 0x13 Stop timer.
void KS_delay(TASK task, TICKS period) 0x14 Block specified task for a period of time.
void KS_execute(TASK task) 0x15 Start a task from its beginning address.
KSRC KS_deftask(TASK task, PRIORITY priority, char *stack, size_t stacksize, void (*entry)(void)) 0x16 Define the attributes of an inactive task.
TASK KS_alloc_task(void) 0x17 Allocate the next available Task Control Block from the pool of free TCBs.
void KS_terminate(TASK task) 0x18 Stop a task by setting it to INACTIVE.
void KS_suspend(TASK task) 0x19 Suspend a task until resumed or re-executed.
void KS_defpriority(TASK task, PRIORITY priority) 0x1b Define or set priority of task.
void KS_yield(void) 0x1c Voluntary release of control to any other task of the same priority.
SEMA KS_waitm(SEMA *semalist) 0x22 Wait on multiple semaphores.
time_t KS_inqtime(void) 0x24 Get current time-of-day.
void KS_deftime(time_t time) 0x25 Set current time-of-day.
TASK KS_inqres(RESOURCE resource) 0x26 Get owner of resource.
KSRC KS_defres(RESOURCE resource, RESATTR condition) 0x27 Define priority inversion on resource.
void *KS_inqtask_arg(TASK task) 0x28 Get environment arguments of task.
void KS_deftask_arg(TASK task, void *arg) 0x29 Set environment arguments for task.
KSRC KS_defqueue(QUEUE queue, size_t width, int depth, void *body, int currsize) 0x2e Define queue.
int KS_user(int (*func) (void *), void *arg) 0x30 Execute function as if it were kernel service.

The RTXC memory allocation facilities (KS_alloc/free/create_part/alloc_part/defpart/free_part) are not used by retailOS and not built into the service dispatcher, at least on Nano 5G.

Semaphores

The following semaphores are defined in the Nano 3G retailOS:

Number Name Description
0x01 S_FW_PWR_CHANGE
0x02 S_BAT_PWR_CHANGE
0x03 S_USB_PWR_CHANGE
0x04 S_CNA_CHANGE
0x05 S_WHEEL_CHANGE
0x06 S_DISKMGRQ
0x07 S_TOPPLUG_SWITCH
0x08 S_RTCTIMERMGR
0x09 S_ALARM_01
0x0a S_ALARM_02
0x0b S_ALARM_03
0x0c S_WATCHDOG
0x0d S_CPUMGRQ
0x0e S_PCFPOWERMGR
0x0f S_POWER_STATE_AC
0x10 S_CGR_STATE_TMR
0x11 S_DEEPSLEEP
0x12 S_ALARM_DONE
0x13 S_PIEZOMGR
0x14 S_PIEZOMGRSNDR
0x15 S_PIEZODONE
0x16 S_ACCPOWER
0x17 S_ACC_REINIT
0x18 S_TOPPLUGSENSER
0x19 S_TOPPLUGCHANGE
0x1a S_BTMCONNECT
0x1b S_BTMPLUGCHANGE
0x1c S_BTMREVERIFY
0x1d S_BTMREVERTIMED
0x1e S_BTMVERCOMP
0x1f S_TOPACCPKTRCVD
0x20 S_BTMACCPKTRCVD
0x21 S_SERIALIDRCVD
0x22 S_UARTATXEMPTY
0x23 S_UARTBTXEMPTY
0x24 S_HDDSCANCOMP
0x25 S_BL_ON
0x26 S_BL_OFF
0x27 S_BL_RAMPDOWN
0x28 S_BL_RAMPUP
0x29 S_BL_TIMESUP
0x2a S_BATT_TIMESUP
0x2b S_BATT_AC_PWR
0x2c S_BATT_TMR_RST
0x2d S_GRAPHMGR
0x2e S_VBL
0x2f S_DTVRECOVERY
0x30 S_CM_HEADPHONE
0x31 S_CM_EXTPOWER
0x32 S_CM_ACCATTACHED
0x33 S_CM_DAC_SETUP
0x34 S_ATAWRKLPRDY
0x35 S_RTXCBUG
0x36 S_BLOCKDEVICE
0x37 S_BLOCKDEVICEQ
0x38 S_DISPLAY
0x39 S_ARB_READY
0x3a S_I2C_DONE
0x3b S_VSYNC

There are three more semaphores (0x3c, 0x3d, 0x3e) that have no name defined and are likely unused. Anything 0x3f and up is a 'Dynamic' semaphore defined at runtime (which we haven't reversed yet).

Queues

The following queues are defined in the Nano 3G retailOS:

Number Name Description
0x01 PIXORESQ
0x02 PIXOSEMAQ
0x03 POSIXRESQ
0x04 POSIXSEMAQ

Mailboxes

The following mailboxes are defined in the Nano 3G retailOS:

Number Name Description
0x01 M_DISKMGR
0x02 M_PIEZOMGR
0x03 M_GRAPHMGR
0x04 M_BLOCKDEVICE
0x05 M_DISPLAY

Resources

The following lockable resources are defined in the Nano 3G retailOS:

Number Name Description
0x01 GPIO_REG_WRITE
0x02 GPIO_INT_INIT
0x03 RTC_TIME_ADJUST
0x04 RTC_ALARM_ADJUST
0x05 I2C_MASTER
0x06 USB_GRANT
0x07 USB_RESP_INIT
0x08 USB_RESPONDER
0x09 DISKPWRMGRSEND
0x0a PIEZOMGRSEND
0x0b SERIALVERIFIER
0x0c RESISTORVERIFIER
0x0d FW_IRAM
0x0e ACCPOWER
0x0f UARTA
0x10 UARGB
0x11 PMU_LOCK
0x12 ADC_LOCK
0x13 DTV_ENC_INIT
0x14 BACKLIGHT

External links

  • https://web.archive.org/web/20230224105131/https://twitter.com/johnwhitley/status/1451952369248264201