关于Matlab的p文件解析
每次github上有人提issue时都要重新看代码来回顾整个过程,遂写下这篇文章减少复习的时间。
什么是p文件
p文件是指Matlab中的以.p为后缀的脚本文件,这种文件经过了编码、编译、压缩的操作,没有进行加密,所以p文件并不是一种安全的、能够保护代码和数据的文件。
p文件使用pcode指令生成,以下是官方的pcode指令的部分说明:
pcode(item)
对搜索路径中.m
文件或文件夹中的代码进行混淆处理,并生成扩展名为.p
的 P 代码文件。例如,如果item
是名为mytest.m
的.m
文件,则生成的文件是mytest.p
。如果item
是一个文件夹,则该文件夹中的所有脚本或函数文件都经过混淆处理并保存在当前文件夹中。
pcode(item1,item2,...,itemN)
基于以逗号分隔的列表中指定的每个.m
文件或文件夹创建 P 代码文件。pcode 算法在 MATLAB 7.5(R2007b 版)中重新设计。如果您的 P 文件是在 MATLAB 7.5 之前生成的,则它不会在 MATLAB 8.6(版本 R2015b)或更高版本中运行。在 7.5 或更高版本中生成的文件无法在 MATLAB 7.4 或更早版本中运行。
P 文件优先于相应的 MATLAB 代码文件 (.m) 执行,即使在修改代码文件后也是如此。
MATLAB 不显示原始 MATLAB 代码文件 (.m) 中可能存在的任何帮助注释。
目前已收集的p文件有三个版本,它们分别有着不同的开头特征。
序号 | 开头特征 | 适用版本 |
---|---|---|
1 | P-file | R2007A之前的版本(含) |
2 | v00.00v00.00 | R2007B之后的版本(含) |
3 | v01.00v00.00 | R2015B之后的版本(含)(不确定) |
目前,Matlab在R2022A版本可以使用更复杂的混淆处理算法,生成的p文件只能在 MATLAB R2022A及更高版本中运行,该版本的p文件目前未收录。
v00.00v00.00和v01.00v00.00
这两个版本的处理过程接近一样,故此处放在一起讲。
p文件结构
以下是一份p文件的数据:
用C语言结构表示如下:
struct pfile_t
{
char major[6];
char minor[6];
uint32_t scramble;
uint32_t crc;
uint32_t uk2; //unknown
uint32_t size_after_compress;
uint32_t size_before_compress;
uint8_t* pdata;
};
解扰
p文件第一步就是解扰,处理函数位于m_parser.dll,代码如下:
static const uint32_t g_scrambling_tbl[256] = {
0x050F0687,0xC3F63AB0,0x2E022A9C,0x036DAA8C,0x32ED8AE2,0xF5571876,0xC66FE7F3,0x6CF0D7C0,
0xBE08BA59,0x0CBB32BE,0x2E1E76F9,0x5B095029,0xD7B83753,0xB949C2EA,0x002B7101,0x10BF6F59,
0x5A565564,0xCF31F672,0x49B64869,0x30B5AE91,0x33D84C72,0xE4B5B87D,0x97EF0BD8,0x58A53999,
0xA2D54211,0x040D16F3,0x8ED0F2AB,0xA1123692,0x7CAD41FD,0x47FD2EE5,0xD5B56675,0x01BC4884,
0x8BF36995,0x83B79111,0x8529F311,0x3EE0F477,0x790EA987,0x4B99DB04,0x2BD1CC37,0x371763E1,
0x58550DC3,0xD9F04330,0x1220B40A,0xB00D4516,0x133A061B,0x924C250C,0x40CCB470,0x6D905B7F,
0x617E1B7E,0x0A82FCD9,0x1E460A11,0x155667F0,0x6F38B557,0x363515E9,0x6DFBA189,0x920DF768,
0x3A422CDD,0x7CCC9435,0xB3202DFB,0x36EF6EDA,0x44C9C31A,0x08D59470,0xB8ABB75E,0x50BD2CAF,
0x8C8D2582,0x3DD5AA6F,0x0F9E2126,0x059BCF09,0x096F8574,0x3B6FED5D,0x3CB332EA,0x61C49337,
0x9560308D,0x4ED3E6F5,0x91D1D84D,0xA89A36A8,0xE1200C01,0xD29E8CBD,0x162A9228,0x429E277F,
0x5D218997,0x34709C39,0x57F48F70,0x4C5A3EEE,0x6AA5B222,0xC5F030F9,0xDE683656,0xA4E7DEFF,
0xC2BCC52E,0x11886451,0xDBD74DD9,0x87868848,0x1A5DF8C2,0x14830538,0xD843B4F7,0x26EB1E44,
0x5258AFA7,0xE7E1D61D,0x2C86ED4D,0x5BC8351B,0x2351C37A,0x693A2038,0x3D8CC852,0xB8B1F408,
0x380E072D,0x4F5EA0A0,0xE14C2AB0,0x192E132E,0xA1FD2D5D,0xF776BCD8,0x5BCC3AAD,0xFF1EB6F4,
0xABE75911,0x33C0CA1D,0xCB78F5E2,0x168D0B34,0xF9B0FB17,0xA9E12C39,0xBB74EA33,0x3C6DC045,
0xBB69908A,0x174C380D,0x43F4488E,0x55C7894C,0xABCF3D45,0x9C37FD85,0x7CB2A790,0xFE27ECEC,
0x8419D3A3,0x293994DE,0x59F02208,0xA76B971D,0x1273B516,0x177CEA5A,0x601D8B25,0x4A81BC43,
0x66DB8AFA,0xC169B5D6,0x63AFCF71,0x08D8B858,0x38E072AE,0x3F7C0A1E,0x87F76F4C,0x64C7CBC0,
0xF33CD43C,0xD370652F,0x7B54D6F4,0x6CEDCF53,0x7D519168,0xB6C9C127,0xA95B8F98,0xB8BB21F2,
0xCE15F934,0xED4FD826,0x8E82AB3F,0x79E53679,0x0987D5AC,0x8B3552CF,0x780D2366,0x8DA1A94F,
0xB46EE7AD,0x51FD456E,0x350D406C,0xC6E29CC3,0x697A2FC8,0x952ACB92,0x11645906,0xD055BAC3,
0x56948168,0x75142877,0xD92E731B,0x8F74F416,0xB4903296,0x6125E267,0xF43CBFD6,0x27CD06D2,
0xB4964796,0xEF9196CA,0x14BAD625,0xB1E7D8FE,0x265B57F2,0xBE1665BD,0xEAA2FAF1,0xF4715126,
0x2B663DE4,0x7925A630,0x6E5687A0,0xB4EE1390,0x045AF8FF,0x6663AB06,0x428FBCDF,0xB8C9E0AD,
0x3860F074,0xF79CFD4B,0xFFAC7D70,0x21DB203C,0x0CC7C8DD,0x9110D677,0xF230DAFF,0x635C4A45,
0x8624FEEE,0x4B5F4E1A,0xF2D13E5C,0x3AB53184,0xAC853082,0x670DFE32,0x62823856,0x611B7818,
0xD69F94FD,0xF73D0E7B,0x13035117,0xFCFAECEF,0x35537439,0xFDA64C08,0xF16C3E15,0xE0B9B21D,
0xF6CBF238,0xDFC2C5B5,0x15A7C5AD,0xFB26EB37,0xC62670BB,0x5837828C,0xB3F0CBE4,0xFE87612F,
0xCFD47FD7,0x339D4955,0xA062816C,0xDC9C48B5,0xC4AE1FCC,0x92935C6B,0x3FF892FA,0x4AD31EBA,
0xDDF2AA86,0xB2C9D156,0x8588503F,0x0A77DB08,0x19D7CF89,0xE80A8895,0xEB935320,0xF0776486,
0x5F479711,0xFE96A437,0xED725175,0x949B0B4A,0x7C3CF03F,0x5EDE8F8A,0x7554BD67,0xF308E277,
0xBEA15540,0x0AFC8314,0xEE2FCDAF,0x04C7C5FB,0x633405A0,0x22209993,0x834F272B,0x33088577,
};
static void descrambling(struct pfile_t* pfile)
{
uint32_t i;
uint8_t scramble_number;
uint32_t* pdata = (uint32_t*)pfile->pdata;
scramble_number = (uint8_t)(pfile->scramble >> 12);
for (i = 0; i < pfile->size_after_compress / 4; i++)
{
*pdata ^= g_scrambling_tbl[(uint8_t)(i + scramble_number)];
pdata++;
}
}
解压
解完扰的数据就可以进行解压,解压使用了zlib算法(使用inflateInit_函数初始化)。
相关函数位于m_parser.dll,解压函数位于zlib.dll,使用zlib提供的uncompress接口。
解码
解压后,得到可以被Matlab解析的代码片段。这些片段有点像Java的class文件预编译的字节码(或者python预编译的pyc文件),需要将这些片段重新转换为代码文本。
预编译和解析的代码位于mtok.dll。
代码片段也有一个十分简单的结构,如下:
token数据依次保存在一个缓冲区中,第一个token的资源ID记为128,依次递增。
二进制数据分为单字节码和多字节码。单字节码主要由Matlab自带的关键字编译得到(如function、switch、while等),多字节码主要是对token的引用。
单字节码最多有128种,解析很简单,一个映射表即可完成。
多字节码有2字节和3字节两种形式,由资源ID的大小来决定用那种形式。
X表示资源id可以使用的比特
res_id <= 0x3FFF ==> 0x8? 0x??(10XXXXXX XXXXXXXX) ==> 2字节
res_id > 0x3FFF ==> 0xC? 0x?? 0x??(11XXXXXX XXXXXXXX XXXXXXXX) ==> 3字节
可见res_id最大值为0x3FFFFFF(4,194,303)
小于0x80的res_id为matlab内部预留
组合
虽然能够将代码片段中的二进制数据一一对应到特定文本上,但是可读性太差,还需要进一步组合这些文本。
此处我为Matlab自带的token设置了一些属性,在组合时会根据这些属性来决定是否添加空格、缩进和换行。
此外,我还添加了一些规则:
- 如果自定义token之前是matlab关键字则加上一个空格
- token属性为TK_CODE_HEAD时增加一个缩进(4个空格)
- token属性为TK_CODE_END时减少一个缩进
- token属性为TK_CODE_LINE时换行
以下是对比截图,可以看到注释确实没有被编译:
P-file
在Matlab R2007A及之前的版本,使用pcode命令生成的p文件是另一种结构。
以下是一份P-file文件的样例:
在分析完P文件的编译过程后,合理怀疑该版本的p文件可能是Matlab的试水作。
p文件有很多子版本,如上图子版本为2.8。频繁的版本迭代说明了当初在设计pcode时缺少了很多考量。
p文件会将依赖的m文件一并编译,一个完整的p文件实际上是多个p文件结合的结果,所以该版本的p文件空间利用率是不如新版的。
p文件的结构设计更加类似COFF结构,函数符号、常量、导入信息、二进制代码都是分开存放的。
解扰
p文件没有过于复杂的编码,函数符号、常量、导入信息、二进制代码都需要解扰。
解扰代码如下:
static void decode(uint8_t* data, int size, uint32_t init)
{
int i;
uint32_t mask;
if (init == 0xBB)
mask = 0x3800B;
else if (init == 0x74)
mask = 0xB537;
else
return; //plain code
for (i = 0; i < size; i++)
{
mask = (0x2455 * mask + 0xC091) % 0x38F40;
data[i] ^= mask;
}
}
解码
二进制代码是一个基于栈的代码,它与新版的p文件不同,二进制代码不能与matlab关键字一一对应,还有一个很大的区别是该版本的二进制代码存在跳转指令。它还有以下一些特点:
- 使用栈运行代码
- 参数从左到右送入栈
- 指令个数为108
- 跳转使用偏移地址