Monday, October 30, 2017

Looking inside Taito C-Chip

This is a long standing WIP. Although this is not complete, thought we'd give an update as to where this project is and where its going.


EDIT: we forgot to add background information. There's some info by Haze here

Package analysis

Among the samples received are some TAITO TC0030CMD "C-CHIP" integrated circuits.

Although generally little is known about this, a previous decap revealed its a multi-chip module (MCM). Given these are relatively scarce, we started by analyzing an x-ray someone took:

The package is filled with all kinds of goodies attached to an etched substrate!
This lines up with the previous decap, but also shows the missing bit:

Unfortunately, the above sample is missing the MCU section, which is critical for our analysis. After some discussion we decided we need to decap one ourselves to better understand the surrounding circuitry. Preparing for decap:

This was then filled with a relatively large pool of fuming nitric acid, heated, and rinsed, until the substrate was revealed:

And we have a full decap! Here's a close up of an NEC uPD4464A SRAM:


 An NEC 65012-229 ASIC:

 An uPD78C11 MCU:

We then imaged the MCU ROM:

to allow analyzing the boot ROM firmware for test modes.

UPD78C11 mask ROM analysis

The primary aim of analyzing the boot ROM is to find test modes that allow reading out the EPROM. Such methods might include:
  • Find a test mode to directly read out EPROM
  • Find a way to load code into MCU to have it dump ROM ("trojan")
  • Repeatedly compute checksums and write bits, seeing if checksum changes
  • Analyze protection mechanisms, such as those that might require glitching
With that in mind, lets take a look.

Here is some early boot code:

; We land here if the “secret handshake” didn’t pass, i.e. the “normal” case.
; clear internal RAM
00000514: 34 00 FF           LXI     HL,$FF00
00000517: 6A FF              MVI     B,$FF
00000519: 60 91              XRA     A,A
0000051B: 3D                 STAX    (HL+)
0000051C: 52                 DCR     B
0000051D: FD                 JR      $051B
; zero the stack pointer
0000051E: 04 00 00           LXI     SP,$0000
00000521: 69 F0              MVI     A,$F0
00000523: 40 29 0F           CALL    $0F29 ; Set_bank
00000526: 40 43 05           CALL    $0543 ; boot_mask_checksum
00000529: 69 01              MVI     A,$01
0000052B: 70 79 01 14        MOV     ($1401),A
; main “loop”
0000052F: 70 69 01 14        MOV     A,($1401)
00000533: 67 03              NEI     A,$03 ; is 0x1401 == 0x03?
00000535: 54 69 05           JMP     $0569 ; sub_command_handler
00000538: 67 02              NEI     A,$02 ; is 0x1401 == 0x02?
0000053A: 54 0C 20           JMP     $200C ; run the eprom code
0000053D: 67 04              NEI     A,$04 ; is 0x1401 == 0x04?
0000053F: 54 00 00           JMP     $0000 ; reset the mcu
00000542: EC                 JR      $052F ; is 0x1401 anything else? Goto main
; end of main “loop”

We know that under normal circumstance (a game booting) it reaches this point and waits for "command 0x02" (run the EPROM code).

For reference, addresses 0x1401, 0x1402, 0x1403 are between the UPD78C11 and the 68K, these act as communication ports for sending / receiving commands.  They are used extensively by the games.

0x1000 – 0x13ff: banked RAM window. Also shared between 68K and UPD7811
0x2000 – 0x3fff: where EPROM lives in UPD78C11 space, not visible to 68K

However, there are a couple of other options in this table: one to reset the MCU and another to jump to an internal sub-command handler. This sub-command handler is a sort of "test mode" for the MCU and offers several debug features.  The idea of the trojan was to exploit these commands by changing the initial value sent by the 68K from 0x02 to 0x03 and make use of a specific command that would copy the EPROM area of the ROM to RAM, as documented later.

Here's the sub-command handler:

; sub_command_handler
; set bank to 0
00000569: 69 F0              MVI     A,$F0
0000056B: 40 29 0F           CALL    $0F29 ; Set_bank
; set high two portF bits to 11 to disable PROG and ???
0000056E: 64 05 C0           MVI     PF,$C0
; set portA data to 0xFF
00000571: 64 00 FF           MVI     PA,$FF
; set portA mode to output
00000574: 69 00              MVI     A,$00
00000576: 4D D2              MOV     MA,A
; set portA data to 0xFF AGAIN [why?]
00000578: 64 00 FF           MVI     PA,$FF
; write 0x0a to $1401 status/mode reg (1010)
0000057B: 69 0A              MVI     A,$0A
0000057D: 70 79 01 14        MOV     ($1401),A
; set REG_C to 0x00
00000581: 6B 00              MVI     C,$00
; set REG_B to 0x00
00000583: 6A 07              MVI     B,$07
; load HL with 0x05A7 which points to a table of test indices
00000585: 34 A7 05           LXI     HL,$05A7
; read ($1401) to A
00000588: 70 69 01 14        MOV     A,($1401)

; compare secondary test value with table of valid test commands at 5A7 and use the ‘offset’ into the table at 5a7 as the parameter for the TABLE opcode at 596
0000058C: 70 ED              NEAX    (HL+)
0000058E: C4                 JR      $0593
0000058F: 43                 INR     C
00000590: 52                 DCR     B
00000591: FA                 JR      $058C
00000592: EE                 JR      $0581
00000593: 0B                 MOV     A,C
00000594: 60 C1              ADD     A,A
00000596: 48 A8              TABLE
00000598: 21                 JB
00000599: AE 05              ; Program_all_banks (0B)
0000059B: 34 06              ; Program_and_verify_all_banks (0C)
0000059D: E4 06              ; Eprom_blank_check (0F)
0000059F: 0F 07              ; Eprom_verify (14)
000005A1: 79 07              ; Eprom_sum16 (12)
000005A3: A7 07              ; Reset_mcu (04)
000005A5: AA 07              ; Eprom_unlock (17) ?
; table of valid ‘sub-commands’ which can be written by host to $1401 (external 0x401); these correspond to the 7 TableSubroutines above.
000005A7: 0B 0C 0F 14 12 04 17

We intended to exploit the EPROM unlock command, which expects you to feed a 128 byte key (magic word) in (with careful timing) at which point it will copy the content of the EPROM to the RAM. Details here:

; Eprom_unlock - compare the block at 81B-89A against data input through $1402.
000007AA: 64 05 C0           MVI     PF,$C0
000007AD: 34 1B 08           LXI     HL,$081B ; point HL at the table
000007B0: 6A 7F              MVI     B,$7F ; length of table
000007B2: 24 80 01           LXI     DE,$0180 ; delay 0x180 times
000007B5: 23                 DCX     DE
000007B6: 0C                 MOV     A,D
000007B7: 60 9D              ORA     A,E
000007B9: 48 0C              SK      Z
000007BB: F9                 JR      $07B5
000007BC: 70 69 02 14        MOV     A,($1402) ; read 402
000007C0: 70 FD              EQAX    (HL+) ; equal to table?
000007C2: 4E 37              JRE     $07FB ; mismatch
000007C4: 52                 DCR     B
000007C5: EC                 JR      $07B2 ; match
; if match and B=0, fall through

; dump_eprom_to_sram
000007C6: 34 00 20           LXI     HL,$2000
000007C9: 86                 CALT    ($008C) ; SetRAMBank0
000007CA: 40 0B 08           CALL    $080B ; dump_hl_to_sram_page
000007CD: 87                 CALT    ($008E) ; SetRAMBank1
000007CE: 40 0B 08           CALL    $080B ; dump_hl_to_sram_page
000007D1: 88                 CALT    ($0090) ; SetRAMBank2
000007D2: 40 0B 08           CALL    $080B ; dump_hl_to_sram_page
000007D5: 89                 CALT    ($0092) ; SetRAMBank3
000007D6: 40 0B 08           CALL    $080B ; dump_hl_to_sram_page
000007D9: 8A                 CALT    ($0094) ; SetRAMBank4
000007DA: 40 0B 08           CALL    $080B ; dump_hl_to_sram_page
000007DD: 8B                 CALT    ($0096) ; SetRAMBank5
000007DE: 40 0B 08           CALL    $080B ; dump_hl_to_sram_page
000007E1: 8C                 CALT    ($0098) ; SetRAMBank6
000007E2: 40 0B 08           CALL    $080B ; dump_hl_to_sram_page
000007E5: 8D                 CALT    ($009A) ; SetRAMBank7
000007E6: 40 0B 08           CALL    $080B ; dump_hl_to_sram_page
000007E9: 40 9B 08           CALL    $089B ; Wait0xE10
000007EC: 40 9B 08           CALL    $089B ; Wait0xE10
000007EF: 69 18              MVI     A,$18
000007F1: 70 79 01 14        MOV     ($1401),A
000007F5: 40 9B 08           CALL    $089B ; Wait0xE10
000007F8: 54 69 05           JMP     $0569 ; sub_command_handler

; mismatch...
000007FB: 70 7A 03 14        MOV     ($1403),B ; write how far we got in the table comparison to 403
000007FF: 69 19              MVI     A,$19
00000801: 70 79 01 14        MOV     ($1401),A
00000805: 40 9B 08           CALL    $089B ; Wait0xE10
00000808: 54 69 05           JMP     $0569 ; sub_command_handler

; dump_hl_to_sram_page
0000080B: 24 00 10           LXI     DE,$1000
0000080E: 14 00 04           LXI     BC,$0400
00000811: 2D                 LDAX    (HL+)
00000812: 3C                 STAX    (DE+)
00000813: 13                 DCX     BC
00000814: 0A                 MOV     A,B
00000815: 60 9B              ORA     A,C
00000817: 48 0C              SK      Z
00000819: F7                 JR      $0811
0000081A: B8                 RET

The UP78C11 has two different outcomes from this function that the 68K should be able to see:
  • Failure writes
    • 1401 = 0x19
    • 1403 = number of bytes that were correct
  • Pass writes
    • 1401 = 0x18
As the exact timing between the 68k and 78C11 is unknown, and the only result we can get from this command is "the number of correct values" that were passed, we have to try and brute force the timings needed.  Too slow or too fast will result in failure.

To do this we made a table on the 68K side containing a "delay" value for each of the 128 bytes that needed sending.

I sent each byte, waited the delay in my table, sent the next byte etc. (not caring if the UPD78C11 code was accepting or rejecting that specific byte).

At some point during this stream of bytes being sent the UPD78C11 would respond, giving us the position of the last byte that was actually successfully received by the internal code.

Once we had that response we could stop sending, because we knew the position last byte that had been successfully received.

With this knowledge the timing delay could be adjusted, and the whole process tried again until a timing window that worked for each byte was found.  Essentially we reduced the delay for that byte, then restarted the process.

By repeating this process until the timing was correct for each byte we were able to send the whole key, and, in MAME, using hacked up Volfied code managed to pass the key check and trigger the "copy to RAM" process, at which point the 68K was able to see whatever we’d put in the C-Chip EPROM area.

Above: early test rig before using a full PCB

The code in MAME was extensively tested and worked for a wide margin of timing videos, allowing for the 68K and UPD78C11 to be running up to 10x different speed than we were guessing.  It was robust, and SHOULD have worked on a PCB.

It didn’t

We are speculating that the following line is to blame:

0000056E: 64 05 C0           MVI     PF,$C0

This is called immediately after going into the sub-command handler.

At that point, the 68K no longer saw any of the status bytes in the 0x1401, 0x1403 comms ports, as if access to the ports had been blocked or remapped.  The UPD87C11 also no longer seemed to respond to commands written to these ports (such as the one to reset the MCU) suggesting that this ‘MVI PF, $C0’ was completely disabling the communication area between the CPUs.

We know the CPU entered the sub-command handler, because we know it was waiting for command 0x02 at the point where we sent command 0x03 on startup, meaning we knew exactly where it was in it’s internal code, but after that line is executed we no longer see any of the expected responses.

It’s possible it remaps it to somewhere else, but limited evidence meant we had no way of knowing this.

The only other place PF is written is early in startup, with another fairly odd piece of code.  From what we could tell PF goes to the ASIC so could be doing anything.

; from RST
; disable interrupt
000001E5: BA                 DI
; Mask all interrupts
; figure 5-24
000001E6: 64 07 FF           MVI     MKL,$FF
000001E9: 64 06 FF           MVI     MKH,$FF
; Memory mapping
; pg 25, figure 4-9
; 0x0E:
; External access enable
; 16 KB
000001EC: 69 0E              MVI     A,$0E
000001EE: 4D D0              MOV     MM,A
; Figure 4-10: Mode F Register (MF)
; configures I/O for input vs output
; 1 => input
000001F0: 69 3F              MVI     A,$3F
000001F2: 4D D7              MOV     MF,A
; A/D channel mode
000001F4: 64 80 0F           MVI     ANM,$0F
; Set ASIC upd4464 SRAM bank to bank 0
000001F7: 69 F0              MVI     A,$F0
000001F9: 70 79 00 16        MOV     ($1600),A
; Ports A-C all inputs
000001FD: 69 FF              MVI     A,$FF
000001FF: 4D D2              MOV     MA,A
00000201: 4D D3              MOV     MB,A
00000203: 4D D4              MOV     MC,A
; zero the latter 3 of the ASIC RAM/Semaphore bytes
00000205: 69 00              MVI     A,$00
00000207: 70 79 01 14        MOV     ($1401),A
0000020B: 70 79 02 14        MOV     ($1402),A
0000020F: 70 79 03 14        MOV     ($1403),A

; for (B = 0x12, B > 0; ++B)
; Move immediate data byte to register.
00000213: 6A 12              MVI     B,$12
; Decrement register and skip next instruction if borrow.
00000215: 52                 DCR     B
00000216: FE                 JR      $0215

if ((adc_regs[0] == 0x80) || have CY interrupt) {
    PF = 0x40;
} else {
    PF = 0xC0;
; CR0 ADC result?
; conversion result register
; unclear why comparing with 0x80
00000217: 4C E0              MOV     A,CR0
; Skip next instruction if immediate data byte equal to register
00000219: 77 80              EQI     A,$80
; Skip next instruction if no interrupt flag is set.
0000021B: 48 1A              SKN     CY
0000021D: C4                 JR      $0222
; neither condition
0000021E: 64 05 C0           MVI     PF,$C0
00000221: C3                 JR      $0225
; ADC or have interrupt
00000222: 64 05 40           MVI     PF,$40

So while in theory, and in MAME the "read out EPROM" area brute force trojan worked by exploiting this internal test mode of the UPD78C11, on real hardware it absolutely did not, leaving us with little to go on.

Brute force

We also considered a few other things like glitching or relatively small package modifications like taking control of a few bus lines.

One option is to directly read out the EPROM by rebonding it. Although this is a lot of work, it is relatively straightforward and will work if done correctly. Also getting a single full ROM extracted may ease boot ROM analysis by seeing how the game uses it. Finally getting a single game completed would be very valuable in the interest of progress.

We first came up with a plan how to cut it out of the package:

 Then planned out a PCB to hold it:

And modeled them together (scaled images) to make sure everything would reasonably fit together:

Note the actual board is cut out in the center and the chip rests on a carrier PCB below (shown later). This allows the bond wires to drop down straight onto the chip. Otherwise, if attached from the same plane, they would need to be formed to avoid hitting the edge of the die.

First, pins were removed from the package and it was backthinned to reveal the PCB:

Backthinning is required to keep a low profile on the lower mezzanine.

Anyway, cut to size, using the PCB traces / vias as a guide:

And then epoxied onto the bottom mezzanine:

And then stacked into the full assembly:

This was then bonded as done in previous posts:

Finally, this was dropped into an EPROM reader. Unfortunately, the connections are flaky and we were only able to get half of the ROM (one address line not connected). The discolored connections above are from re-dissolving some of them with nitric acid to attempt rebonding flaky connections.

Next steps

We are looking into a few options to proceed such as better understanding the ASIC. For example, there is at least one pin we don't understand well that could be required to activate the test mode. We may also re-capture the boot ROM to ensure we are analyzing the right code.

However, we are likely going to get access to a bonding machine in the near future. This will hopefully make rebonding the EPROM die relatively straightforward. Stay tuned for more info!

Enjoy this post? Please support us on Patreon! Note: with the Indiegogo campaign over we unfortunately don't currently have a way to accept one time donations.

Monday, August 21, 2017

HD647180 Ghox/Whoopee bonus round

In an earlier post we looked at processing firmware from HD647180 chips in the original decap lot. However, there are several related MCUs that were not included: Ghox and Whoopee:

Although we aren't generally accepting new chips, these were evaluated on case-by-case basis to be both easy to process and of moderate interest.

Like previous chips, we milled a cavity for acid:

Then decapped and masked them:

Which was followed with soldering to adapter boards and exposing to UV light.

Finally, they were inserted into an EPROM reader and which successfully extracted the data.

Enjoy this post? Please support us on Patreon! Note: with the Indiegogo campaign over we unfortunately don't currently have a way to accept one time donations.

Monday, July 24, 2017

Gotta capture 'em all!

The TGP ROM die images are going through extended processing as detailed here.

If you get some time swing on by and capture some bits!

Other: while there is a suggestion box, the sourcecode is also available on github if you want to directly make a pull request.

Tuesday, May 2, 2017

Decap #145: Croupier

The Croupier IC markings have been removed:

Unlike some other obfuscated/remarked chips we did not have identification leads from the arcade community. So we decapped it to gather more information:

Clearly made by microchip:

Cross referencing 97074 with public copyright records yields "97074 PIC16C74 CMOS PIC." Great!

Next, we acquired samples to practice on. One of them was decapped with pure WFNA which, unlike the real chip, badly corroded the leadframe but was easily fixed with silver epoxy:

Note bond pads were not damaged, only leadframe. Anyway, the second sample used WFNA/H2SO4 mix and so didn't suffer from this:

We attempted to program and UV erase the PIC with a mask:

but were unable to clear the security fuse. Oh no!

Fortunately, we are not the first person to encounter this and so tried to erase at an angle:

This had mixed results, but trying sharp angles angles (sharper than above) and long exposure times generally worked. We played a little with different sides of the chip. In all tests main EPROM was untouched.

Not perfect, but should be good enough for the real chip. Masking:

And tried a few angles until the security fuse got cleared. Yipee!

Enjoy this post? Please support us on Patreon! Note: with the Indiegogo campaign over we unfortunately don't currently have a way to accept one time donations.

Monday, May 1, 2017

Decap #139 replacement: Mortal Kombat 4 U76

A bit back we processed PIC16C57 samples and discovered#139 (Invasion U76) was partially defective as received. We knew this before decap since PIC16C57 allows reading a 4 bit XOR of the 12 bit flash worlds even when protected. Unfortunately, only 2/4 word bits changed in the protected readout and only 3/12 word bits changed in the unprotected version.

Like Invasion, Mortal Kombat 4 is built on the Midway Zeus platform. With this in mind, a team member donated U76 from Mortal Kombat 4 to process as a replacement. Label off:

And here's it with the main EPROM masked against UV light:

Which allows safely erasing security fuses like in previous posts.

Stay tuned for our post on identifying and extracting #145 (Croupier)!

Enjoy this post? Please support us on Patreon! Note: with the Indiegogo campaign over we unfortunately don't currently have a way to accept one time donations.

Thursday, February 23, 2017

Fujitsu MB86233 "TGP" DSP

In this post we look at obtaining the Fujitsu MB86233 ROM for the following:
  • #14 Sega 315-5571
  • #15 Sega 315-5572
  • #16 Sega 315-5573

 First decap a sample chip to get more information.  Here's 315-5571:

Which tells us it's contact programmed:

Under 20x brightfield illumination:

Our computer vision expert was concerned automatic capture would be noisy and asked for a 100x die scan.  Unfortunately, 100x takes a long time.  Fortunately, a quick test showed a 20x scan with crossed polarizers has sufficient SNR:

We then fed this into computer vision tools like on 39.  Finally, ROM bit order was swapped around until it disassembled relatively cleanly.  Unfortunately, instruction set documentation is incomplete meaning even correct bit ordering may produce disassembler errors.

Chips around #211 - 218 are suspected to be similar chips and we might take a look at them in the near future.

Enjoy this post?  Please support us on Patreon or Indiegogo!


To be clear, 14-16 were all decapped and captured (gotta catch'em all!)

#14 Sega 315-5571:

#15 Sega 315-5572:

Similar to above, but takes up slightly more code space.

#16 Sega 315-5573:

This one uses basically all of the code space.  There's also minor overglass delamination in two spots.  Here's the worse one:

Unclear what caused this, but fortunately bits are not obscured by it.  If they were, could probably be removed with brief HF wash.

Saturday, January 28, 2017

Besting #204 Bad Dudes (EI31.9A)

Side note: post was supposed to go out early release to backers but accidentally went to general release.  We apologize to the backers, but it got a lot of attention in the short time that it was out, so we think it is best at this point to just keep it out there.  We'll fix this for future posts.

First part is same idea as #45 but on a smaller scale: assess and repair damage.  Sample as received:

Close up:

The chip has been opened and 5 pins are missing at the bottom.  Bond wires are in general disarray and the die is dirty:

However, no obvious silicon damage.  After ultrasonic clean:

This allowed a closer inspection under a higher power microscope that also revealed no damage.

Among the 5 broken wires are 3 address bits, reset, and one from P3.  P3 is unused during readback, so we really only need to fix 4 of them.

Start by rebuilding leadframe.  First, epoxy everything still intact:

This helps prevents damage when inserting into a socket.  Next, inserted into a socket to improve mechanical stability and provide a template for the new pins:

Then a sacrificial 8751 was stripped of its pins (see 45 post for process details) and epoxied into the leadframe:

During wire attach, previous work put epoxy on the die surface to use as a fine palate.  Although this is likely harmless, can possibly scratch the die and/or short something out.  Decided to put the security fuse UV mask on early to double as a mask and a protective barrier:

Wires are also soldered up now:

And add epoxy:

After cure tried dumping and still getting address failures with several bits stuck at 0.  Although pin resistance values are not in spec to other similar pins, they are close.  Suspect that epoxy got too close to die edge and is doing some sort of soft short.

Used WFNA (white fuming nitric acid) to dissolve epoxy and try again:

Re-applying epoxy:

And address bits are now solid!

However, this whole time data bits have also been flaky.  At least one wire was broken and was repaired with silver conductive epoxy.  However, we are unable to determine why data lines are flaky.

Fortunately, dumps were taken at many steps of this process.  In fact, at least three dumps had all address bits correct but varying data bits bad.  Several of these dumped the same twice meaning the data is incomplete but reasonably stable.

Wrote a small bit of code to combine the good bits from each dump.  This also showed that dumps agree on bits common between them.

Next, popped combined dump into a disassembler to verify reasonable control flow.  Cursory check revealed no errors.  Finally, dump was simulated and showed no obvious issues.

In conclusion, 204 was salvageable but was a bit of a puzzle to piece together.  Several verification methods indicate its probably good.  Hopefully this is the last of the samples requiring such repairs.

Enjoy this post?  Please support us on Patreon or Indiegogo!