一、基本原理
考虑截获PHP读取源文件的接口。一开始,我考虑从Apache和PHP之间的接口处处理,参见apache的src/modules/php4/mod_php4.c(这个是PHP用static方式编译进apache,makeinstall后的文件),在send_php()函数中截获文件指针,采用临时文件的方式,解密后替换文件指针。这种方法经过测试实践,证明是可行的。但是,必须使用两次文件操作,效率低下,而且对于DSO方式不可采用。双缘敬老院
由此,重新考虑截获PHP读取文件并装载至缓存的过程,经过费力的寻找,发现在Zend引擎中zend-scanner.c是做此处理的。开始对此文件修改。照明工程
二、实现方法示意
采用libmcrypt作为加密模块,现在采用的是DES方法ECB模式加密,
下面是文件加密的源代码:
C++代码
/*ecb.c-------------------cuthere-----------*/
/*encryptforphpsourcecodeversion0.99beta
weareusinglibmcrypttoencryptcodes,please
installitfirst.
compilecommandline:
gcc-O6-lmcrypt-lm-oencryptphpecb.c
pleasesetLD_LIBRARY_PATHbeforeuse.
GNUcopyleft,designedbywangsu,miweicong*/
#defineMCRYPT_BACKWARDS_COMPATIBLE1
#definePHP_CACHESIZE8192
#include<mcrypt.h>
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
main(intargc,char**argv)
{
inttd,i,j,inputfilesize,filelength;
charfilename[255];
charpassword[12];
FILE*ifp;
intreadfd;
char*key;
void*block_buffer;
void*file_buffer;
intkeysize;
intdecode=0;
intrealbufsize=0;
structstat*filestat;
if(argc==3){
strcpy(password,argv[1]);
strcpy(filename,argv[2]);
}elseif(argc==4&&!strcmp(argv[1],"-d")){
strcpy(password,argv[2]);
strcpy(filename,argv[3]);
decode=1;
printf("Enteringdecodemode...n");
}else{
printf("Usage:encryptphp[-d]passwordfilenamen");
exit(1);
}
keysize=mcrypt_get_key_size(DES);
key=calloc(1,mcrypt_get_key_size(DES));
gen_key_sha1(key,NULL,0,keysize,password,strlen(password));
td=init_mcrypt_ecb(DES,key,keysize);
if((readfd=open(filename,O_RDONLY,S_IRUSR|S_IWUSR|S_IRGRP))==-1){
printf("FATAL:Can'topenfiletoread");
exit(3);
}
filestat=malloc(sizeof(stat));
fstat(readfd,filestat);
inputfilesize=filestat->st_size;
printf("filesizeis%dn",inputfilesize);
filelength=inputfilesize;
inputfilesize=((int)(floor(inputfilesize/PHP_CACHESIZE))+1)*PHP_CACHESIZE;
if((file_buffer=malloc(inputfilesize))==NULL){
printf("FATAL:can'tmallocfilebuffer.n");
exit(2);
}
if((block_buffer=malloc(PHP_CACHESIZE))==NULL){
printf("FATAL:can'tmallocencryptblockbuffer.n");
exit(2);
}
j=0;
while(realbufsize=read(readfd,block_buffer,PHP_CACHESIZE)){
printf(".");
if(!decode){
if(realbufsize<PHP_CACHESIZE){
for(i=realbufsize;i<PHP_CACHESIZE;i++){
((char*)block_buffer)[i]='';
}
}
mcrypt_ecb(td,block_buffer,PHP_CACHESIZE);
}else{
mdecrypt_ecb(td,block_buffer,realbufsize);
}
memcpy(file_buffer+j*PHP_CACHESIZE,block_buffer,PHP_CACHESIZE);
j++;
}
close(readfd);
if((ifp=fopen(filename,"wb"))==NULL){
printf("FATAL:fileaccesserror.n");
exit(3);
}
fwrite(file_buffer,inputfilesize,1,ifp);
free(block_buffer);
free(file_buffer);
free(filestat);
fclose(ifp);
printf("n");
return0;
}
/*---endofecb.c------------------------------------*/
因为ECB模式是块长度确定的块加密,这里填充了一些空字符。国际展览
然后,修改php代码中Zend/zend-scanner.c如下:
(我的php版本是4.01pl2,SUNsparc/solaris2.7,gcc2.95;)
文件前加入:
#defineMCRYPT_BACKWARDS_COMPATIBLE1
#include<mcrypt.h>
然后,注释掉大约3510行前后的YY_INPUT的定义。
然后,修改大约5150行前后的yy_get_next_buffer()函数:
函数头加上定义:
void*tempbuf;
char*key;
chardebugstr[255];
inttd,keysize;
intx,y;
FILE*fp;
然后,注释掉
YY_INPUT((&yy_current_buffer->yy_ch_buf[number_to_move]),
yy_n_chars,num_to_read);
这一句。
改为:
tempbuf=malloc(num_to_read);
if((yy_n_chars=fread(tempbuf,1,num_to_read,yyin))!=0){
/*decode*/
#definepassword"PHPphp111222"
#definedebug0
keysize=mcrypt_get_key_size(DES);
key=calloc(1,mcrypt_get_key_size(DES));
gen_key_sha1(key,NULL,0,keysize,password,strlen(password));
td=init_mcrypt_ecb(DES,key,keysize);
mdecrypt_ecb(td,tempbuf,yy_n_chars);
memcpy((&yy_current_buffer->yy_ch_buf[number_to_move]),tempbuf,yy_n_chars);
if(debug){
fp=fopen("/tmp/logs","wb");
fwrite("nstartn",7,1,fp);
fwrite(tempbuf,1,yy_n_chars,fp);
fwrite("nenditn",7,1,fp);
fclose(fp);
}
}
free(tempbuf);
然后,编译php,按正常方法安装即可,因为我对于libtool不太熟悉,因此我选择static方式,并在configure时加入了--with-mcrypt,这样我就不用自己手工修改Makefile电缆桥架
三、测试及结果
编译php,apache后,用ecb.c编译出来的encryptphp加密了几个文件,分别为<1K,10K+,和40K+,在处理40K大小文件时出错,别的文件均正常。塑胶地板
这是因为块的ECB加密方式决定了必须使用定长块,所以,请诸位同好指点采用何种流加密方式可以兼顾到zend每次读取8192字节的缓存处理方式。(其他平台上zend每次读取的块长度可能有所不同)