PRX loader for IDA

PRX loader for IDA


什么是PRX

PRX是PSP RelocatebleeXecutable的缩写。Sony对标准的ELF文件格式进行了扩展,他因为

使用了特殊的ELF类型、重定义了ProgramHeader中物理地址的含义、增加了特殊的Sections以及采用了自定义的重定位格式而有别于标准的ELF。这也是为什么IDA的ELF文件加载器不能很好的支持PRX格式的根本原因。下面详细描述。

 

特殊的ELF类型

PRX中使用了0xFFA0作为他的文件类型,而不是标准MIPS ELF中的0x0002。这是区分PRX和ELF的主要特征。

同时在ELF头的flags里看到CPU扩展为0xA2,这表示PSP上所使用的allegrex处理器。

 

ProgramHeaders物理地址的含义

对于PRX文件,至少应该包括一个Program Header,这样重定位的工作才可以进行,程序才能够被加载。Program Header中物理地址这一项并不像ELF文档中所说的那样,用于将数据加载到指定的内存地址。第一个Program Header中对应的物理地址的低31位[30:0],其实是.rodata.sceModuleInfo这个Section在prx文件中的偏移地址。如果这个prx是内核模式的,那么第一个Program Header里的物理地址最高有效位应该被置1。从第2个Program Header开始,物理地址都是0。

 

加入了特殊的Sections

这些sections具体说来是下面10种:

.sceStub.text

.lib.ent.top

.lib.ent.btm

.lib.ent

.lib.stub.top

.lib.stub.btm

.lib.stub

.rodata.sceModuleInfo

.rodata.sceResident

.rodata.sceNid

 

.sceStub.text

系统调用的填充段,为下面这样的格式:

sceXXX:

jr $ra

nop

 

例如:

.sceStub.text:0892D1A4                                     sceKernelCreateThread:

.sceStub.text:0892D1A4                 jr      $ra

.sceStub.text:0892D1A8                 nop

当PSP的加载器loadcore.prx加载完成后,我们从内存上反汇编的结果是:

0x0892D1A4: 0x03E00008 '....' -                       jr         $ra

0x0892D1A8: 0x0008840C '....' -                      syscall    0x2210

可以看到nop被一个确定的系统调用给替换了。

 

下面3个Sections,和输出符号信息有关,表示哪些库是存在于该prx中,可以被导出被别的prx使用。但该库中具体哪些符号被导出却是记录在.rodata.sceResident。

.lib.ent.top

用来标记输出符号信息段的顶部,大小固定为4个字节,是一个32bit的0x00000000

.lib.ent.btm

用来标记输出符号信息段的尾部,大小固定为4个字节,是一个32bit的0x00000000

.lib.ent

输出符号信息段,包含了该prx向外导出的符号。例如Syslib里的module_start/stop;sceLibc、sceLibm里和符号,以及用户自己的函数库。

 

数据结构如下:

/* Structure to hold the module exportinformation */

struct PspModuleExport

{

         u32name;        //32bit word Addr: Name ofExport Library (default: 0)

         u32flags;                   // u16 BCDVersion

// u16 module attributes

         u32counts;      // u8 size of export entry indwords

                                     //u8 number of variables

                                     //u16 number of Functions

         u32exports;    //32bit word Addr:__entrytable in .rodata.sceResident

};

Note:

如果name为0(也就是上面说的默认值),表示这是SysLib

 

下面3个Sections,和输入符号信息有关,表示该prx用到了系统哪些库里的符号。但具体是哪些符号,记录在.sceStub.text中,其符号的名称通过对保存在.rodata.sceNid列表中的对应nid值进行查表,获得。

.lib.stub.top

用来标记输入符号信息段的顶部,大小固定为4个字节,是一个32bit的0x00000000

.lib.stub.btm

用来标记输入符号信息段的尾部,大小固定为4个字节,是一个32bit的0x00000000

.lib.stub

输入符号信息段,罗列了prx中所引用的系统函数。

数据结构如下:

/* Structure to hold the module importinformation */

struct PspModuleImport

{

         u32name;        //32bit word Addr:__stub_modulestr in .rodata.sceResident

         u32flags;                   // u16 ImportFlags

                                     //u16 Library Version

         u32counts;      // u16 Number of Stubs toImport

                                     //u16 Size of the Stub itself (in 32bit words)

         u32nids;           //32bit word Addr:__stub_nidtable in .rodata.sceNid

         u32funcs;         //32bit word Addr: sceXXXstub in .sceStub.text

         u32vars;           // 32bit word Addr: storedthe addr for vars ???

};

Note:

这个vars比较有趣。因为没有遇到过这样的例子,所以无法验证。可能一些系统的prx中的确会导出一些全局的变量,作为别的prx的import来使用,但很罕见。

 

.rodata.sceModuleInfo

用于保存该prx中的模块信息。

数据结构如下:

/* Structure to hold the module info */

struct PspModuleInfo

{

         u32flags;          // u16 Module Attributes

//0x0000 Module starts in User Mode

//0x1000 Module starts in Kernel Mode

//u16 Module Version (2 chars)

         charname[PSP_MODULE_MAX_NAME];

                            //28bytes Module Name (0 terminated)

         u32gp;     //32bit word Addr: GP

         u32exports;    //32bit word Addr:.lib.ent

         u32exp_end;   //32bit word Addr:.lib.ent.btm

         u32imports;    //32bit word Addr:.lib.stub

         u32imp_end;  //32bit word Addr:.lib.stub.btm

};

 

.rodata.sceResident

用于保存输出的符号nid值,以及这些符号在该prx中所对应entry地址。

例如:

.rodata.sceResident:08A4A29Cexport_1_memset_nid:.word 0x10F3BB61

.rodata.sceResident:08A4AA38                 .word memset

 

.rodata.sceNid

类似于sceResident,在sceNid里保存的是输入符号的nid值。但对应的地址保存在.sceStub.text这里。

 

用户自定义的重定位格式:

首先从类型上将,在标准的ELF文件中,重定位的section的类型是9(SHT_REL),在PRX使用的是0x700000A0和0x700000A1,其中0x700000A1是后来加入的一种类型。

其次从重定位的数据结构上说,每个entry就是有2个32bits的数组成,前一个是offset值,后一个包含了重定位的类型以及如何选择被操作指令的地址和基地址。

通过C语言来描述,后面这个32bits是:

// Defines for the r_info field

#define ELF32_R_ADDR_BASE(i) (((i)>>16) & 0xFF)

#define ELF32_R_OFS_BASE(i) (((i)>>8) & 0xFF)

#define ELF32_R_TYPE(i) (i&0xFF)

 

typedef struct {

Elf32_Addr r_offset;

Elf32_Word r_info;

} Elf32_Rel;

 

// MIPS Reloc Entry Types

#define R_MIPS_NONE 0

#define R_MIPS_16 1

#define R_MIPS_32 2

#defineR_MIPS_REL32 3

#define R_MIPS_26 4

#define R_MIPS_HI16 5

#define R_MIPS_LO16 6

#defineR_MIPS_GPREL16 7

#defineR_MIPS_LITERAL 8

#defineR_MIPS_GOT16 9

#defineR_MIPS_PC16 10

#defineR_MIPS_CALL16 11

#defineR_MIPS_GPREL32 12

#define R_MIPS_X_HI16 13

#define R_MIPS_X_J26 14

#define R_MIPS_X_JAL26 15

对于这总共的16中类型,其中7~12和3我们并不进行处理。Tyranid在Prxtool的代码中给出的注释是:/* Unsupported for PRXes (loadcore.prx ignores them) */

 

OFS_BASE告诉我们从哪个program header中选出基地址和r_offset进行叠加。用C来描述:

iOfsPH = rel-> r_info & 0xFF;

dwRealOfs = rel->r_offset +prx->phdr32[iOfsPH].p_vaddr;

如果r_offset为0x100,OFS_BASE为0,而第0个program header被加载的地址是0,那么我们就需要对0x100这个地址上的指令进行修改。

 

ADDR_BASE告诉我们哪个program header中对应的地址是需要被重定位的。用C来描述:

iValPH = (rel-> r_info >> 8) &0xFF;

dwCurrBase = base_addr +prx->phdr32[iValPH].p_vaddr;

例如如果ADDR_BASE是1,program header也是1,第1个program段被加载到了0x1000,当前保存在ELF文件指令中的操作地址是0xf0,那么需要重定位到的地址是0x10f0+ base_addr。其中base_addr是用户期望加载到的基地址,对于eboot.bin来说是0x08804000。

 

 

PRXLDR For IDA

 

搞清楚什么是PRX之后,就比较容易解释我们这个PRX Loader都做了些什么。

l  可以指定地址进行重定位操作。

l  识别出prx的模块信息放在IDAView的顶部。

l  识别出export和import信息,从代码或psplibdoc.xml中找到nid对应的符号名,给符号命名。

l  补全了export中sceLibc和sceLibm里nid对应的符号名。

 

在实现的过程中,我们尽量采用标准C,代码做到简洁易懂。但由于IDA的SDK结构,还是需要使用到C++编译器。

 

代码放在

http://www.kusodev.org/hg/prxldr/file/

为了方便,放一份编译好的文件在

http://download.csdn.net/source/3541053

 

如何编译

Kusodev上的代码预期使用mingw32进行编译。测试环境是gccversion 4.5.2 (GCC)、使用的IDASDK版本为5.5。

首先将prxldr放在IDASDK里的ldr下。

然后使用mingw的shell来到prxldr目录下运行make即可。

 

对于Csdn上这份,加入了VC9的工程(可以使用debug版本进行调试)。使用的IDASDK版本为6.1。mingw也是可以编译过的。同样需要将prxldr放在IDASDK里的ldr目录下。

 

对于VC9,需要根据实际IDA安装目录情况修改,Project Property ->Configuration Properties -> Linker ->Output File以及Project Property ->Configuration Properties -> Debugging ->Command,以保证输出的文件可以放到IDA的loaders目录下,以及调试的时候可以启动IDA。

 

使用

将prxldr.ldw放在IDA安装目录的loaders下。拖入prx文件。选择使用PSP Prx Loader加载。同时psplibdoc.xml文件预期也放在IDA安装目录的loaders下,如果没有找到会弹出对话框要求选择该文件。

 

备注

Relocation

重定位,其目的是使一个通过绝对地址进行函数跳转和数据引用的代码,变成位置无关代码(Position-independent_code)。这是因为PRX类似与动态库,在每次被装载的时候baseaddr可能会不一样。对应没有MMU的系统这样做使得运行多个例程成为了可能。

 

NID

就是给每个符号名分配一个u32的ID。这样做的好处:

变长到定长的处理,数据结构变得整齐,方便信息的存储;

方便比对和查找;

因为算法不可逆,对代码的安全性进行了保护。

http://psp.jim.sh/svn/psp/trunk/nidattack/src/main.c

里的u32 fastSHA1(unsigned char *buffer, int len)函数,描述了算法细节。标准的SHA-1产生的是一个160Bits的摘要信息。

运行下面代码,再去查看psplibdoc.xml文件,就豁然开朗了。

         char*funcname = "sceAtaInit";

         u32nid = fastSHA1((unsigned char*)funcname, strlen(funcname));

         printf("%s: 0x%08x\n", funcname, nid);

 

 

 

参考资料:

1.      Prxtool

http://psp.jim.sh/svn/psp/trunk/prxtool/

2.      yet another PlayStationPortableDocumentation

http://hitmen.c02.at/files/yapspd/psp_doc/

3.      IDA PLUG-IN WRITING IN C/C++

http://www.binarypool.com/idapluginwriting/idapw.pdf

4.      IDA SDK Header Files …


你可能感兴趣的:(数据结构,properties,Module,header,attributes,structure)