此篇文章记录一下我这些天PHP HACKING的经验,乱七遭八的。各位看官莫见笑,有问题多交流。
先大概描述一下PHP内核的情况,PHP内核分为2个部分,ZEND和PHP CORE。ZEND可以说是PHP的内核的内核,他负责与操作系统沟通。分配内存,模块初始化什么什么的,他就负责了,同时把可读的脚本代码系统化。而PHP CORE呢,负责一些外围处理,比如与SAPI沟通。APACHE、IIS啊之类的东西。php.ini他也管。 还负责网络和文件I/O。最后提醒一句,PHP是用C写的,而不是C++,大多数资源,类型,在PHP内核中需要转换。
在C语言中,套接字这样定义:SOCKET socket(int af,int type,int protocol);
在PHP中是:
typedef int PHP_SOCKET; typedef struct { PHP_SOCKET bsd_socket; int type ; int error;} php_socket;
看起来是一样的。那就来看看一个最简单的网络函数。
PHP_FUNCTION(socket_write) { zval *arg1; php_socket *php_sock; int retval, str_len; long length; char *str; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs|l", &arg1, &str, &str_len, &length) == FAILURE) return; ZEND_FETCH_RESOURCE(php_sock, php_socket *, &arg1, -1, le_socket_name, le_socket); if (ZEND_NUM_ARGS() < 3) { length = str_len; } #ifndef PHP_WIN32 retval = write(php_sock->bsd_socket, str, MIN(length, str_len)); #else retval = send(php_sock->bsd_socket, str, min(length, str_len), 0); #endif if (retval < 0) { PHP_SOCKET_ERROR(php_sock, "unable to write to socket", errno); RETURN_FALSE; } RETURN_LONG(retval); }
先说zval,ZVAL是PHP内核中的结构,里面包含任何数据类型。所以在PHP内核中最好用ZVAL,而少用char *。这样可能会带来一些安全问题。看上面的代码,好象是没问题。其实不然,看char * str。他作为第2个参数被传入。
执行socket_write ( $sock, "hellosdfs35434sdvx.plpop34][[]" );
正常得很。但是如果我们提交一个PHP数组他就会暴错了。为什么呢?虽然说char str[]从某种意义上等于char * str。但是PHP中的数组和C语言可不一样。PHP程序本身这种错误太多了,PHP的开发者可能已经习惯了。
继续PHP SOCK.zend_parse_parameters函数是得到传入参数的,当CHAR类型被传入的时候,必须指定CHAR LEN.ZEND_NUM_ARGS()是得到函数个数的。
那TSRMLS_CC是什么东西。这里得专门讲一下TSRM。他的全称是Thread Safe Resource Management。
PHP是作为网络脚本来使用的,这意味着,成千上万的人在同1秒钟里可能调用同一个页面。客户A请求的是ID1,客户B请求的是ID2,以下省去数万字。
如果没有TSRM的话,单线程的SAPI还好说,像apache,iis这样的话。那么PHP可能乱套。TSRM的作用就是保持变量的中立性。在PHP中,基本上从全局传入参数都要使用TSRM.PHP hacking中也应该使用TSRM。
ZEND_FETCH_RESOURCE(php_sock, php_socket *, &arg1, -1, le_socket_name, le_socket);
相当于是把参数1 当做PHP_SOCK类型在ZEND注册资源。关于SOCKET_WRITE后面都是一些简单的C函数,就不谈了这个了。
关于PHP内核里面建立套接字还有一种方法,那就是用PHP流。PHP流是很强大的,是I/O的代名词。无论是FILE I/O,还是NETWORK I/O。
拿php_stream_open_wrapper API来说,他支持FILE、TCP、UDP、SSL、SCP、HTTP、FTP。
php_stream * stream = php_stream_open_wrapper ("http://www.php.net", "rb", REPORT_ERRORS, NULL); if (stream) { while(!php_stream_eof(stream)) { char buf[1024]; if (php_stream_gets(stream, buf, sizeof(buf))) { printf(buf); } else { break; } } php_stream_close(stream); }
看了上面的代码,可以知道的是。C语言的FILE操作函数那些 加上php_stream便可以操作php i/o了。这是官方给的一个例子。有一点错误,PHP中printf是不能输出到网页的,要用php_printf或者zend_printf,php_write。
继续socket的话题。在php i/o中有一个API,可以把C语言的SOCKET劫持到PHP中。
php_stream * php_stream_sock_open_from_socket ( int socket, int persistent )
这样比起php_sock方便一些。
再谈谈PHP内核中的管道与命令执行。
编写API { 建立管道 创建进程 返回输出管道 } 函数 { 传入参数处理 执行API 转换API管道为PHP流 读取PHP流 输出流 }
我看了一下SYSTEM,PASSTHRU的代码,都是由popen来完成。POPEN又由popen_ex来完成。
TSRM_API FILE *popen_ex(const char *command, const char *type, const char *cwd, char *env) { FILE *stream = NULL; int fno, str_len = strlen(type), read, mode; STARTUPINFO startup; PROCESS_INFORMATION process; SECURITY_ATTRIBUTES security; HANDLE in, out; char *cmd; process_pair *proc; TSRMLS_FETCH(); security.nLength= sizeof(SECURITY_ATTRIBUTES); security.bInheritHandle = TRUE; security.lpSecurityDescriptor= NULL; if (!str_len || !CreatePipe(&in, &out, &security, 2048L)) { return NULL; } memset(&startup, 0, sizeof(STARTUPINFO)); memset(&process, 0, sizeof(PROCESS_INFORMATION)); startup.cb= sizeof(STARTUPINFO); startup.dwFlags= STARTF_USESTDHANDLES; startup.hStdError= GetStdHandle(STD_ERROR_HANDLE); read = (type[0] == 'r') ? TRUE : FALSE; mode = ((str_len == 2) && (type[1] == 'b')) ? O_BINARY : O_TEXT; if (read) { in = dupHandle(in, FALSE); startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE); startup.hStdOutput = out; } else { out = dupHandle(out, FALSE); startup.hStdInput = in; startup.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); } cmd = (char*)malloc(strlen(command)+strlen(TWG(comspec))+sizeof(" /c ")); sprintf(cmd, "%s /c %s", TWG(comspec), command); if (!CreateProcess(NULL, cmd, &security, &security, security.bInheritHandle, NORMAL_PRIORITY_CLASS, env, cwd, &startup, &process)) { return NULL; } free(cmd); CloseHandle(process.hThread); proc = process_get(NULL TSRMLS_CC); if (read) { fno = _open_osfhandle((long)in, _O_RDONLY | mode); CloseHandle(out); } else { fno = _open_osfhandle((long)out, _O_WRONLY | mode); CloseHandle(in); } stream = _fdopen(fno, type); proc->prochnd = process.hProcess; proc->stream = stream; return stream; }
process_pair是一个结构体,HANDLE和FILE。这段代码好象是一个标准的CMDSHELL。他建立了并返回了管道(文件)流。
这可是用C语言建立的管道啊,PHP并不懂的。要用PHP的一个未公开的API来完成转换。说到未公开,PHP的内核好多东西都是"未公开"的。这方面的文档少得很。
php stream * pipe = php_stream_fopen_from_file_pipe(fp,"rb");
接下来就交给php_stram系列处理了。这样就能正常输出了。
关注天下网吧微信,了解网吧网咖经营管理,安装维护: