dekuNukem / Nintendo_Switch_Reverse_Engineering

A look at inner workings of Joycon and Nintendo Switch
3.4k stars 188 forks source link

Firmware analysis (ROM + Patch RAM) + RAM #41

Open CTCaer opened 6 years ago

CTCaer commented 6 years ago

@shuffle2 So I found time to play with the OTA FW commands on the Joy-Con.

It seems that RAM is partitioned to 2 regions: 0x0D0000-0x0DFFFF and 0x200000-0x247FFF which match the 353KB RAM of Bcm20734.

I did 2 consecutive ram dumps with controller reboot in-between. I also added some extra that may help you.

You can find them here.


Also, I have difficulties loading the rom vanilla style or with your script in IDA. Addresses and other stuff do not match in the disassembly..

As I understand, 1st uint32LE (0x200400) is stack pointer and 2nd (0x201 THUMB) is reset vector. So I used many combinations in rom address offset and loading addres with 0x200 (because thumb), 0x0 and 0x4 (offset to remove the 1st uint32) but nothing solid.

I also tried your script and then loaded additional binary (rom) with various loading offset and file offset but I could not find anything familiar in the .c file produced.

Can you help me on what to input on the various fields in IDA ("Disassembly memory organization", "Load additional binary file")?

I will also try to load the ram dumps to fill the RAM voids. BTW, If I do this, it will override the patched addresses?

CTCaer commented 6 years ago

I found sth familiar (process_cmd()):

int __fastcall sub_21B2A8(int a1, int a2, int a3, _BYTE *a4, int a5)
{
  int v5; // r7@1
  int v6; // r9@1
  _BYTE *v7; // r8@1
  int result; // r0@1
  unsigned int v9; // r5@1
  int v10; // r2@2
  int v11; // r3@2
  int v12; // r0@2
  int v13; // r12@8
  int v14; // r0@10
  signed int v16; // r0@15
  int v17; // r0@39
  char v18; // r1@44
  int v19; // [sp+0h] [bp-168h]@51
  int v20; // [sp+134h] [bp-34h]@1
  int v21; // [sp+138h] [bp-30h]@1
  int v22; // [sp+13Ch] [bp-2Ch]@1
  _BYTE *v23; // [sp+140h] [bp-28h]@1

  v20 = a1;
  v21 = a2;
  v22 = a3;
  v23 = a4;
  v5 = a1;
  v6 = a3;
  v7 = a4;
  result = sub_72344((unsigned __int16)a5);
  v9 = result;
  if ( result )
  {
    sub_72786(result, v7);
    v12 = *(_DWORD *)(v5 + 468);
    if ( v12 == v21 && *(_BYTE *)(v12 + 4) == 2 )
    {
      if ( v6 != 2 )
        return sub_724B4(v9);
      if ( *v7 == 146 )
      {
        sub_21B25C(v5, (int)v7, 6u);
        if ( a5 )
          sub_21B25C(v5, v13 + 7, a5 - 7);
        *(_BYTE *)(v5 + 4734) = 1;
        v14 = sub_72786(v9, v7 + 7);
        *(_DWORD *)(v5 + 616) = sub_6FBC8(v14);
      }
    }
    else if ( v6 != 2 )
    {
      return sub_724B4(v9);
    }
    if ( *(_DWORD *)(v5 + 68) == v21 && byte_DB2B2 )
    {
      byte_221B05 = *(_BYTE *)(v9 + 1) & 0xF;
      return sub_724B4(v9);
    }
    v16 = *(_BYTE *)v9;
    if ( v16 != 20 )
    {
      if ( v16 > 20 )
      {
        if ( v16 == 145 )
        {
          sub_21F808(v5, v9);
        }
        else if ( v16 > 145 )
        {
          if ( v16 == 147 )
          {
            sub_727F4(2238403);
            sub_72786(2238410, v9 + 7);
            byte_22263C = -109;
            word_22263E = 38;
            sub_21CCF8(1, (int)&byte_22263C, 0x2Du);
          }
          else if ( v16 == 165 )
          {
            sub_22019E(v5, v9, v10, v11);
          }
        }
        else if ( v16 != 31 && (v16 == 67 || v16 == 83) )
        {
          sub_220206(v5, v9, (unsigned __int16)a5);
        }
        return sub_724B4(v9);
      }
      if ( v16 == 17 )
        return sub_724B4(v9);
      if ( v16 <= 17 )
      {
        if ( v16 == 1 )
        {
          if ( *(_BYTE *)(v5 + 4735) )
          {
            sub_727F4(&byte_222B10);
            sub_727F4(&byte_222D10);
            byte_222B10 = 5;
            if ( *(_BYTE *)(v9 + 10) == 72 )
            {
              if ( *(_BYTE *)(v9 + 11) == 1 )
                v18 = 72;
              else
                v18 = 73;
              byte_222B11 = v18;
            }
            else
            {
              byte_222B11 = 1;
            }
            sub_72786(&byte_222B12, v9 + 2);
            v17 = *(_BYTE *)(v9 + 10);
            if ( v17 != 32 && v17 != 33 && v17 != 34 )
              sub_21EE8A(313, &byte_222B10, &byte_222D10);
          }
          else
          {
            sub_21E7FE(v9 + 2);
          }
          sub_21D9AA(v5, v9);
          if ( !*(_BYTE *)(v5 + 4735) )
            return sub_724B4(v9);
          goto LABEL_49;
        }
        if ( v16 != 3 )
        {
          if ( v16 != 16 )
            return sub_724B4(v9);
          if ( *(_BYTE *)(v5 + 4735) )
          {
            sub_727F4(&byte_222B10);
            sub_727F4(&byte_222D10);
            byte_222B10 = 5;
            byte_222B11 = 1;
            sub_72786(&byte_222B12, v9 + 2);
            sub_21EE8A(313, &byte_222B10, &byte_222D10);
LABEL_49:
            byte_221B0A = 0;
            return sub_724B4(v9);
          }
          goto LABEL_58;
        }
        sub_727F4(&v19);
        sub_72786(&v19, v9 + 10);
        sub_220326((signed int)&v19);
        return sub_724B4(v9);
      }
      if ( v16 == 18 )
      {
        sub_21E7FE(v9 + 2);
        sub_727F4(&byte_22263C);
        sub_72786((char *)dword_222640 + 3, v9 + 10);
        return sub_724B4(v9);
      }
      if ( v16 != 19 )
        return sub_724B4(v9);
    }
LABEL_58:
    sub_21E7FE(v9 + 2);
    return sub_724B4(v9);
  }
  return result;
}

I used your script, then loaded ROM with 0x4 for loading offset and file offset and the other 0x0, then ram dumps with appropriate loading offsets, then T 0x1.

That's your way or should I do sth more?

shuffle2 commented 6 years ago

To load rom, you should load at 0 (just use defaults) and choose ARMv7-M.

ARMv7-M architecture has the initial SP address at offset 0 from the current exception vector, so loading with this value @ 0 is the correct thing to do.

For my patchram loader, note that I wrote it before rom was dumped. So it is not complete. If you look at the parser, you can see that it skips over some record types. To make the loader complete, someone needs to reverse the patchram-loading code within rom to figure out how it handles those other record types, and then implement them in the parser/loader.

Note IDA API has various ways to load data. One of them is like "patching", which allows you to revert the patch (see original value) within IDA at a later time. By default IDA just overwrites existing data and forgets about it. So this behavior could be added to the loader if needed.

As an aside, I typically add mmio regions to IDA as I discover them. Here is example from the idb I used for patchram reversing (rom is not loaded): capture Create by edit->segment->add-> choose class=DATA, check "sparse"

CTCaer commented 6 years ago

I'll try to find how they are used (found a function with hardcoded read to SPI @x10000, maybe it's around there).

The good thing with the ram dumps, is that they make the code more complete, but they hardcode a lot of things as expected.

BTW, if you want any RAM dump from a Joy-Con (R) or a special one with specific configuration done first, or a read from a specific memory region, don't hesitate to ask.

Lastly, here are some ram patches I collected.

Anyway, thank you! These help a lot. I'll use your suggestions asap.

CTCaer commented 6 years ago

@shuffle2 Here are some more dumps from the right joy-con. It includes RAM and some memory regions I found in the code (0x260000, 0x310000-0x360000,0x600000, 0x640000, 0x650000, 0xE0001000-0xE0042000).

Some are missing. Either the device hangs on read or they are zeroed.

If you missed the previous files (JC L ram and ram patches): Here's the MEGA folder.

shuffle2 commented 6 years ago

Thanks @CTCaer , however I'm not actively working on joycon stuff at the moment. I just wanted to figure out flash format and dump rom...which i did :)

CTCaer commented 6 years ago

@shuffle2 sorry to pester you again, but I need some help. I couldn't wait for my programmer to arrive, so I went ahead and changed only one specific byte in the active PatchRAM and the joy-con again does not power on.

  1. How you managed to patch yours and it powered on?
  2. Did you find any checksum that must also be changed?

(About 2.: For sure the x0A do not have a checksum, maybe another record keeps the checksum?)

shuffle2 commented 6 years ago

How did you patch yours? If you used ROM protocol, what exactly did you do?

There's no checksum or anything that needs to be adjusted. I just changed content of 0x0a record without changing its length.

CTCaer commented 6 years ago

Hmm, strange. I've used many times the ROM protocol to change only one byte from the magic for DS location and worked every time. The firmware was always loading the 0x10000 patchram. And then I was writing the value back

Now I tried to change a byte in an instruction for the x11 subcmd. This change renders the check for x6000 <= address <= x10000 useless, so I can write everywhere in SPI easily without feature reports.

So I sent, like usual:

Enable x73/x74 cmd:
- x70
Erase the following byte in a x0A record in DS2 :
- x73   88 16 02 F8   01 00   CKSM
Write new value to the same byte:
- x74   88 16 02 F8   01 00   07   CKSM

And after the reboot, the joycon died again. I cant wait for my programmer. Not to fix the joy-con, but to check what the hell is happening

CTCaer commented 6 years ago

Internally, the writing subcmd for SPI is done like this: So even if you change 1 byte, it reads the whole sector, changes the value and then erases and writes the whole sector.

But with feature report, seems to not do it this way. Maybe I hit a bug and I must send the whole sector for it to work. But it's strange that with xF8001FF4 works...

EDIT: I corrected the first paragraph, in which I'm talking about spi write subcmd. Not the feature report memory erase/write.

shuffle2 commented 6 years ago

yea 2 things you probably want to do: 1) ensure you operate on a multiple of the flash erase block size (check the spec sheet). this is why you see code that operates on more than 1 byte. 2) read back the data to make sure it was written correctly. If check fails, you can at least prevent resetting the device until you try to manually fix the problem.

can you still interact with rom handlers after fw crashes? if not, maybe you can try holding spi clk or cs pin high while turning it on, to prevent it reading from flash? just use tweezers or little piece of wire...

CTCaer commented 6 years ago
  1. I see. I will check the spec and I will still try to write (subcmd) a sequence of numbers to x9000 sector and try to change a byte (ft erase/write) and read back(subcmd/ft read) and see how it behaves.
  2. No, you can't interact with the device at all. It actually stucks in a bootloop. I will try this method though. I really want to see if it can properly work with the default code. (But still it will not find a BD_ADDR).
CTCaer commented 6 years ago

@shuffle2 That's what you get when you don't read the datasheet..

The first time I erased the whole 0x0 sector and now the whole 0x21000 sector.. (So, it seems that before, I never read the whole magic at x1FF4...)

So x73 works like this: addr & xFFF000 reject size Send x73 ADDRES //24bit

And the chip internally even if you send directly an address inside a sector, it still uses only the 12 MSB and erases that sector.

Then you can program whatever size you want.

The simple thing dammit! the simple things.. Thanks for opening my eyes. I'll wait for my programmer now.

Edit: Btw, I tried the CS or SCLK but nothing. To be true it was difficult to keep the tweezer and trying to plug the battery, so I may messed up every time trying. I will check this normally by erasing the 0x0 sector PatchRAM when my programmer arrives.