Hacking Pandora's Box Game Cartridge 2

Disclaimer

This blog is intended solely for educational purposes. Please refrain from using this information for malicious purposes.

Introduction

I recently acquired a new Pandora’s Box game cartridge, which utilizes the RK3128 system-on-a-chip (SOC) from Rockchip. I wanted to share my experience with hacking it.
The manufacturer has overclocked it to 1.5GHz to ensure smooth gameplay with HD video filtering. However, due to the increased performance, an additional fan is required for cooling. Fortunately, the cartridge uses an SD card for booting, making it easy to dump the firmware.Here is a photo of the PCB:
PCB

loader

The system initiates the “loader” program to extract necessary files and launch the main program, “mkemu.” The manufacturer has placed the “loader” in the initram filesystem of the kernel, which is in CPIO format. Extracting the “loader” binary is a straightforward process.
The following code snippet from /etc/init.d/rcS in the initram reveals how the system starts the “loader” program:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/sh
PATH=/bin:/sbin:/usr/bin:/usr/sbin
runlevel=S
prevlevel=N
umask 022
export PATH runlevel prevlevel

mount -t tmpfs mdev /dev
mkdir /dev/pts
mkdir /dev/shm

mount -a

echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

touch /dev/mdev.log

cd /tmp
mkdir -m 1777 .X11-unix

cd /lib/modules/3.4.39-h3
insmod elib.ko
#insmod ch341.ko

while true
do
/home/games/loader
done

Analyzing the Loader Program

I loaded the “loader” program into Ghidra and performed an automated analysis. At address 0x11b10, it becomes evident that the “loader” attempts to mount a LUKS filesystem file:

1
2
sprintf(uStack_620,"echo -n \"%s\" | cryptsetup luksOpen /dev/dm-1 pass -",&DAT_00028c94);
system(uStack_620);

The LUKS key is stored in ASCII format at address DAT_00028c94 and is calculated using a number read from /dev/elib. The function at 0x14720 includes the code to read the original number from /dev/elib:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int read_rand_seed(void *data)

{
int __fd;
int iVar1;

__fd = open("/dev/elib",0);
elib_fd = __fd;
if (__fd < 0) {
iVar1 = -1;
}
else {
iVar1 = ioctl(__fd,0x80045a13,data);
iVar1 = iVar1 >> 0x1f;
close(__fd);
elib_fd = -1;
}
return iVar1;
}

Fortunately, the programmer compiled the Linux driver into an individual .ko file instead of including it in the kernel binary. This allows us to easily locate the code responsible for returning the number within the small .ko file.

Analyzing elib.ko

We can find elib.ko in the CPIO filesystem. By loading it into Ghidra, we can locate the code that handles the 0x80045a13 ioctl code in the elib_ioctl function. Ghidra’s decompiled C code provides the following information:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
if (param_2 == 0x80045a13) {
local_2c = (undefined *)0x0;
memset(&local_28,0,0x10);
pcVar2 = strstr(saved_command_line,"custom_id=");
if (pcVar2 != (char *)0x0) {
pcVar2 = pcVar2 + 10;
pcVar3 = strchr(pcVar2,0x20);
if (pcVar3 == (char *)0x0) {
__n = strlen(pcVar2);
}
else {
__n = (int)pcVar3 - (int)pcVar2;
}
if (__n < 0x10) {
strncpy((char *)&local_28,pcVar2,__n);
local_2c = (undefined *)simple_strtoul(&local_28,0,0x10);
}
}
if (local_2c == (undefined *)0x0) {
local_2c = (undefined *)0x87654321;
}
printk("\x011custom id: %x\n",(uint)local_2c);
puVar6 = *(undefined **)(((uint)local_38 & 0xffffe000) + 8);
bVar12 = (undefined *)0xfffffffb < param_3;
if (!bVar12) {
bVar12 = puVar6 < param_3 + 4 || (uint)((int)(param_3 + 4) - (int)puVar6) < (uint)bVar12;
}
if (!bVar12) {
puVar6 = (undefined *)0x0;
}
if (puVar6 != (undefined *)0x0) {
return 4;
}
ppuVar4 = &local_2c;
uVar5 = 4;
goto LAB_00010668;
}
if (param_2 != 0x80045a0b) goto LAB_00010674;

And the programmer is lazy, as he hardcoded this sensitive number. It’s 0x87654321. I noticed there is an MCU on the PCB, and I suspected the programmer might have stored the number in the MCU, but he didn’t.

Find the key for the LUKS file

Now, it’s time to calculate the key for the LUKS file. I decided to use unicorn engine for this task, I wrote a python script for this task.
This script requires the preparation of the file rocky.rsa and the number 0x87654321. The thumb instructions call the standard libc functions, memset and memcpy. I simulate these two functions in the block_hook section. The following is the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if(address==0x10cb4):  
# void * memset(void *__s,int __c,size_t __n)
s = uc.reg_read(UC_ARM_REG_R0)
c = uc.reg_read(UC_ARM_REG_R1)
n = uc.reg_read(UC_ARM_REG_R2)
print('hook memset', hex(s), c, n)
uc.mem_write(s, bytes([c]*n))
uc.reg_write(UC_ARM_REG_R0, s)
uc.reg_write(UC_ARM_REG_PC, uc.reg_read(UC_ARM_REG_LR))
elif(address==0x10d2c):
# void * memcpy(void *__dest,void *__src,size_t __n)
__dest = uc.reg_read(UC_ARM_REG_R0)
__src = uc.reg_read(UC_ARM_REG_R1)
__n = uc.reg_read(UC_ARM_REG_R2) #TODO: hack
print('hook memcpy', hex(__dest), hex(__src), __n)
dat = uc.mem_read(__src,__n)
uc.mem_write(__dest,bytes(dat));
uc.reg_write(UC_ARM_REG_R0, __dest)
uc.reg_write(UC_ARM_REG_PC, uc.reg_read(UC_ARM_REG_LR))

If the script succeeds, we can get the LUKS key at address 0x28c94. It is a string, and we can use this key to mount the LUKS file on a PC.

Decrypt config.dat file

The function at 0x011dc8 is a decryption function. loader uses it to decrypt config.dat and other configuration files for gaming. I wrote a Python script to perform this decryption. The script saves the resulting file to /tmp/tt.bin.

mkemu

loader starts mkemu using system. We can find the related instruction at address 0x01153c in loader. The Ghidra decompiled C code is as follows:

1
2
3
4
5
do {
system("/home/games/mkemu");
puts("User app exit.");
usleep(10000);
} while( true );

Analyzing the mkemu program

mkemu is the essential program for this game cartridge. It displays the game selection menu and allows users to play their selected game. After analyzing it using Ghidra, I believe it utilizes SDL.

Decrypt asset

The programmer encrypted and packaged the assets in the file asset.dat. Therefore, we need to decrypt the assets using the Thumb instructions in mkemu. You can find the complete Python script in here. This script will save each decrypted asset file in the target folder.

Conclusion

This board isn’t very difficult, and I feel like hacking it is similar to participating in a Capture The Flag (CTF) competition. Every key is like a flag. The only difference is that the board is real.

Author

Meng Xipeng

Posted on

2023-08-07

Updated on

2023-10-05

Licensed under

Comments