1 首先,查看所用php编译版本V6/V9 在phpinfo()中查看
2 将php_redis.dll和php_igbinary.dll放在php扩展目录中(ext),并修改配置文件php.ini
添加 扩展的时候一定要
extension=php_igbinary.dll
extension=php_redis.dll
这个顺序
否则重启Apache的时候会出现,PHP startup 错误
3 重新启动服务,查看phpinfo(),下面表示成功;
1 首先,查看所用php编译版本V6/V9 在phpinfo()中查看
2 将php_redis.dll和php_igbinary.dll放在php扩展目录中(ext),并修改配置文件php.ini
添加 扩展的时候一定要
extension=php_igbinary.dll
extension=php_redis.dll
这个顺序
否则重启Apache的时候会出现,PHP startup 错误
3 重新启动服务,查看phpinfo(),下面表示成功;
今天搞了N久的虚拟目录配置,在几乎要放弃的时侯偶然看到一篇文章,将我的问题搞定
原贴地址:http://blog.sina.com.cn/s/blog_6c2e6f1f0100l92h.html
我的需求是这样的,系统有一个专门的文件夹用于存放图片,css,js或者附件,如:
http://www.test.com/resources/images/a.jpg
http://www.test.com/resources/css/a.css
http://www.test.com/resources/js/a.js
http://www.test.com/resources/attach/a.doc
这样的配置对于apache来说那相当容易,
需要通过location uri规则匹配访问到该文件夹,我使用如下配置:
1 2 3 |
location ^~ /resources/ { root d:/www/; } |
试了N多次都能访问不到,一直报404,无比杯具!最后拜读了上面提供的blog才解决,发现跟原博主一样,没有真正搞清楚,location中root和alias的区别,最后修改成:
1 2 3 |
location ^~ /resources/ { alias d:/www/; } |
成功实现了我的需求。
原贴如下:
niginx 似乎没有虚拟目录的说法,但是可以指定请求路径时nginx访问的路径,也算是一个解决办法。
(原文链接 http://ddbiz.com/?p=187)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
server { listen 80 default; server_name _; location / { root html; index 403.html; } location ~ //.ht { deny all; } <strong>location /phpadmin/ { alias /opt/www/phpadmin/; index index.php; }</strong> location ~ /.php$ { include httpd.conf; } } |
要注意的是, location /phpadmin/ {} 和 location /phpadmin {} 是完全不同的。
前者可以访问到目录,而后者将被重定向到服务器,如: http://127.0.0.1/phpadmin ,将被重定向到 http://_/phpadmin
下面这个配置和上面基本类似,唯一的不同是,所有对 /phpadmin/的访问将正确解析,而其他访问则返回页面不存在(404)的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
server { listen 80 default; server_name _; location / { root html; #index 403.html; return 404; } location ~ //.ht { deny all; } <strong>location /phpadmin/ { alias /opt/www/phpadmin/; index index.php; }</strong> location ~ /.php$ { include httpd.conf; } } |
Nginx虚拟目录支持PHP也是花了不少时间查找资料研究摸索,最终用下面两段解决。下面不是一段完整的Nginx配置文件,对于该配置片段简单解释如下:
1. 该文件配置一个主机www.mydomain.com在/data/Service下
2. 将一个在/data/Forum下的论坛程序挂在www.mydomain.com/Forum下,这里我用的是phpBB3
3. 将一个在/data/Mantis下的Bug跟踪管理程序MaintisBT挂在www.mydomain.com/Mantis下
phpBB3和MantisBT分别是两个独立的PHP程序。
对于Nginx虚拟目录支持分两段:
1. 第一段用alias解决虚拟目录问题,使用rewrite处理访问重定向,并传递用于fastcgi的正确的脚本位置
2. 第二段用于处理所有的非PHP文件在虚拟目录中的访问,没有第二段,访问非PHP文件就是出现404
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
server { listen 80; server_name www.mydomain.com; #root /data/Service; charset utf-8; access_log /usr/local/nginx/logs/www.mydomain.com.access.log main; <span style="color: #ff0000;"># Virtual directory with PHPsupport onnginx location ~ ^/Forum/.+\.php$ { alias /data/Forum/; rewrite /Forum/(.*\.php?) /$1 break; fastcgi_index index.php; fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME /data/Forum$fastcgi_script_name; include fastcgi_params; } location ~^/Forum($|/.*) { alias /data/Forum/$1; index index.php index.html index.htm; } # Virtual directory with PHP support on nginx – end</span> # Virtual directory with PHP support onnginx location ~ ^/Mantis/.+\.php$ { alias /data/Mantis/; rewrite /Mantis/(.*\.php?) /$1 break; fastcgi_index index.php; fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME /data/Mantis$fastcgi_script_name; include fastcgi_params; } location ~^/Mantis($|/.*) { alias /data/Mantis/$1; index index.php index.html index.htm; } # Virtual directory with PHP support on nginx – end location /{ root /data/Service; index index.php index.html index.htm; } #fast-cgi for php-cgi location ~\.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /data/Service$fastcgi_script_name; include fastcgi_params; } location ~\.(gif|jpg|jpeg|png|bmp|ico|rar|css|js|zip|java|jar|txt|flv|swf|mid|doc|ppt|xls|pdf|txt|mp3|wma)${ access_log off; expires max; } error_page 404 /404.html; # redirectserver error pages to the static page /50x.html # error_page 500 502 503504 /50x.html; } |
PHP5添加了一项新的功能:Reflection。这个功能使得程序员可以reverse-engineer class, interface,function,method and extension。通过PHP代码,就可以得到某object的所有信息,并且可以和它交互。
假设有一个类Person:
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 39 40 |
class Person { /** * For the sake of demonstration, we"re setting this private */ private $_allowDynamicAttributes = false; /** type=primary_autoincrement */ protected $id = 0; /** type=varchar length=255 null */ protected $name; /** type=text null */ protected $biography; public function getId() { return $this->id; } public function setId($v) { $this->id = $v; } public function getName() { return $this->name; } public function setName($v) { $this->name = $v; } public function getBiography() { return $this->biography; } public function setBiography($v) { $this->biography = $v; } } |
通过ReflectionClass,我们可以得到Person类的以下信息:
只要把类名”Person”传递给ReflectionClass就可以了:
1 |
$class = new ReflectionClass('Person'); |
获取属性(Properties):
1 2 3 4 5 6 7 8 9 |
$properties = $class->getProperties(); foreach($properties as $property) { echo $property->getName()."n"; } // 输出: // _allowDynamicAttributes // id // name // biography |
默认情况下,ReflectionClass会获取到所有的属性,private 和 protected的也可以。如果只想获取到private属性,就要额外传个参数:
1 |
$private_properties = $class->getProperties(ReflectionProperty::IS_PRIVATE); |
可用参数列表:
如果要同时获取public 和private 属性,就这样写:ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED
应该不会感觉陌生吧。
通过$property->getName()可以得到属性名,通过getDocComment可以得到写给property的注释。
1 2 3 4 5 6 7 8 9 10 11 |
foreach($properties as $property) { if($property->isProtected()) { $docblock = $property->getDocComment(); preg_match('/ type=([a-z_]*) /', $property->getDocComment(), $matches); echo $matches[1]."n"; } } // Output: // primary_autoincrement // varchar // text |
有点不可思议了吧。竟然连注释都可以取到。
获取方法(methods):通过getMethods() 来获取到类的所有methods。返回的是ReflectionMethod对象的数组。不再演示。
最后通过ReflectionMethod来调用类里面的method。
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 |
$data = array("id" => 1, "name" => "Chris", "biography" => "I am am a PHP developer"); foreach($data as $key => $value) { if(!$class->hasProperty($key)) { throw new Exception($key." is not a valid property"); } if(!$class->hasMethod("get".ucfirst($key))) { throw new Exception($key." is missing a getter"); } if(!$class->hasMethod("set".ucfirst($key))) { throw new Exception($key." is missing a setter"); } // Make a new object to interact with $object = new Person(); // Get the getter method and invoke it with the value in our data array $setter = $class->getMethod("set".ucfirst($key)); $ok = $setter->invoke($object, $value); // Get the setter method and invoke it $setter = $class->getMethod("get".ucfirst($key)); $objValue = $setter->invoke($object); // Now compare if($value == $objValue) { echo "Getter or Setter has modified the data.n"; } else { echo "Getter and Setter does not modify the data.n"; } } |
有点意思。
PHP反射类ReflectionClass和ReflectionObject是PHP中的扩展反射类,该扩展用来分析php程序,导出或提取出关于类、方法、属性、参数等的详细信息,包括注释。
看一个这样的问题,php类的成员变量没有在类中声明,而是在函数中声明,有什么不同?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class test{ private $name; private $sex; function __construct(){ $this->aaa='aaa'; } } $test=new test(); $reflect=new ReflectionClass($test); $pro=$reflect->getDefaultProperties(); print_r($pro);//打印结果:Array ( [name] => [sex] => ) echo $test->aaa;//打印结果:aaa |
在这个test类中,声明了两个成员变量$name和$sex,但是在构造函数中,又声明了一个变量$aaa,初始化类,使用反射类打印默认成员属性只有声明的两个成员变量属性,但是打印类的$aaa变量发现还是可以输出结果。
请问类的成员变量不用声明,在函数中声明也是可以的吗,有什么区别?
在你这个例子中,使用ReflectionClass是不恰当的,因为__construct只有在实例化class时,才会执行。
也就是说ReflectionClass更多的是反射类声明时的结构,而不是类实例化后的结构,所以没有输出属性aaa是正确,因为属性aaa确实是(在类声明时)不存在的。
那么怎么看属性aaa呢,应该用ReflectionObject反射实例化后的结构,例如
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?php class test{ private $name; private $sex; function __construct(){ $this->aaa='aaa'; } } $test=new test(); $reflect=new ReflectionObject($test); $pro=$reflect->getProperties(); print_r($pro); |
经过实例化以后,属性aaa才会存在,这时你就能看到属性aaa了
因为php是“动态”语言,所以可以类的成员变量不用声明,在函数中声明也是可以的。
如今网络在我们的生活工作中所起的作用越来越大,可以说离开了网络我们就无法正常的工作和生活。作为程序员我们写的程序大多数也会跟网络相关,而想要使用网络首先要将机器的网络配置设置好。而手动设置的方法显然很不可取,所以我们要让程序帮我们完成。下面是一个很常用的C#设置系统各种网络参数的一个小Demo一起看看吧。
这个Demo是通过”Win32_NetworkAdapterConfiguration”这个管理类.这里面已基本包括了IP,DNS,网关的设置信息。
在C#中使用WMI还是比较简单的:
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
using System; using System.Collections.Generic; using System.Text; using System; using System.Collections; using System.Text; using System.Management; using System.Text.RegularExpressions; namespace Demo { /// <summary> /// 网络设置类,设置网络的各种参数(DNS、网关、子网掩码、IP) /// </summary> public class NetworkSetting { public NetworkSetting() { // 构造函数逻辑 } /// <summary> /// 设置DNS /// </summary> /// <param name="dns"></param> public static void SetDNS(string[] dns) { SetIPAddress(null, null, null, dns); } /// <summary> /// 设置网关 /// </summary> /// <param name="getway"></param> public static void SetGetWay(string getway) { SetIPAddress(null, null, new string[] { getway }, null); } /// <summary> /// 设置网关 /// </summary> /// <param name="getway"></param> public static void SetGetWay(string[] getway) { SetIPAddress(null, null, getway, null); } /// <summary> /// 设置IP地址和掩码 /// </summary> /// <param name="ip"></param> /// <param name="submask"></param> public static void SetIPAddress(string ip, string submask) { SetIPAddress(new string[] { ip }, new string[] { submask }, null, null); } /// <summary> /// 设置IP地址,掩码和网关 /// </summary> /// <param name="ip"></param> /// <param name="submask"></param> /// <param name="getway"></param> public static void SetIPAddress(string ip, string submask, string getway) { SetIPAddress(new string[] { ip }, new string[] { submask }, new string[] { getway }, null); } /// <summary> /// 设置IP地址,掩码,网关和DNS /// </summary> /// <param name="ip"></param> /// <param name="submask"></param> /// <param name="getway"></param> /// <param name="dns"></param> public static void SetIPAddress(string[] ip, string[] submask, string[] getway, string[] dns) { ManagementClass wmi = new ManagementClass("Win32_NetworkAdapterConfiguration"); ManagementObjectCollection moc = wmi.GetInstances(); ManagementBaseObject inPar = null; ManagementBaseObject outPar = null; foreach (ManagementObject mo in moc) { //如果没有启用IP设置的网络设备则跳过 if (!(bool) mo["IPEnabled"]) continue; //设置IP地址和掩码 if (ip != null && submask != null) { inPar = mo.GetMethodParameters("EnableStatic"); inPar["IPAddress"] = ip; inPar["SubnetMask"] = submask; outPar = mo.InvokeMethod("EnableStatic", inPar, null); } //设置网关地址 if (getway != null) { inPar = mo.GetMethodParameters("SetGateways"); inPar["DefaultIPGateway"] = getway; outPar = mo.InvokeMethod("SetGateways", inPar, null); } //设置DNS地址 if (dns != null) { inPar = mo.GetMethodParameters("SetDNSServerSearchOrder"); inPar["DNSServerSearchOrder"] = dns; outPar = mo.InvokeMethod("SetDNSServerSearchOrder", inPar, null); } } } /// <summary> /// 启用DHCP服务器 /// </summary> public static void EnableDHCP() { ManagementClass wmi = new ManagementClass("Win32_NetworkAdapterConfiguration"); ManagementObjectCollection moc = wmi.GetInstances(); foreach (ManagementObject mo in moc) { //如果没有启用IP设置的网络设备则跳过 if (!(bool) mo["IPEnabled"]) continue; //重置DNS为空 mo.InvokeMethod("SetDNSServerSearchOrder", null); //开启DHCP mo.InvokeMethod("EnableDHCP", null); } } /// <summary> /// 判断是否符合IP地址格式 /// </summary> /// <param name="ip"></param> /// <returns></returns> public static bool IsIPAddress(string ip) { //将完整的IP以“.”为界限分组 string[] arr = ip.Split('.'); //判断IP是否为四组数组成 if (arr.Length != 4) return false; //正则表达式,1~3位整数 string pattern = @"\d{1,3}"; for (int i = 0; i < arr.Length; i++) { string d = arr[i]; //判断IP开头是否为0 if (i == 0 && d == "0") return false; //判断IP是否是由1~3位数组成 if (!Regex.IsMatch(d, pattern)) return false; if (d != "0") { //判断IP的每组数是否全为0 d = d.TrimStart('0'); if (d == "") return false; //判断IP每组数是否大于255 if (int.Parse(d) > 255) return false; } } return true; } } } |
好了,写好上面这个类以后,就等着哪里需要然后NEW一个就可以了。很简单吧,如果遇到设置失败的情况,可能是因为权限不够,请参考C#默认以管理员身份运行程序
在Vista 和 Windows 7 及更新版本的操作系统,增加了 UAC(用户账户控制) 的安全机制,如果 UAC 被打开,用户即使以管理员权限登录,其应用程序默认情况下也无法对系统目录、系统注册表等可能影响系统正常运行的设置进行写操作。这个机制大大增强了系统的安全性,但对应用程序开发者来说,我们不能强迫用户去关闭UAC,但有时我们开发的应用程序又需要以 Administrator 的方式运行,如何实现这样的功能呢?
下面演示 C# 程序如何实现提示用户以管理员权限运行。
本例以WinForm程序演示,新建一项目生成后进行相应修改:
方法一:通过 System.Diagnostics.Process.Start() 方式启动:
实现方法: 修改默认生成的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 30 31 32 33 34 35 36 |
static void Main(string[] Args) { /** * 当前用户是管理员的时候,直接启动应用程序 * 如果不是管理员,则使用启动对象启动程序,以确保使用管理员身份运行 */ //获得当前登录的Windows用户标示 System.Security.Principal.WindowsIdentity identity = System.Security.Principal.WindowsIdentity.GetCurrent(); //创建Windows用户主题 Application.EnableVisualStyles(); System.Security.Principal.WindowsPrincipal principal = new System.Security.Principal.WindowsPrincipal(identity); //判断当前登录用户是否为管理员 if (principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator)) { //如果是管理员,则直接运行 Application.EnableVisualStyles(); Application.Run(new Form1()); } else { //创建启动对象 System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo(); //设置运行文件 startInfo.FileName = System.Windows.Forms.Application.ExecutablePath; //设置启动参数 startInfo.Arguments = String.Join(" ", Args); //设置启动动作,确保以管理员身份运行 startInfo.Verb = "runas"; //如果不是管理员,则启动UAC System.Diagnostics.Process.Start(startInfo); //退出 System.Windows.Forms.Application.Exit(); } } |
效果:由于是通过System.Diagnostics.Process.Start() 方式外部调用启动,所以直接通过VS运行时,是不会提示VS也需要管理员权限,只有程序本身需要管理员权限,与生成应用程序的程序不同。这点是和方法二实现的主要不同之处。
方法二:通过添加应用程序清单文件:
在 项目 上 添加新项 选择“应用程序清单文件” 然后单击 添加 按钮
添加后,默认打开app.manifest文件,将:
<requestedExecutionLevel level=”asInvoker” uiAccess=”false” />
修改为:
<requestedExecutionLevel level=”requireAdministrator” uiAccess=”false” />
然后打开 项目属性 ,将 应用程序 标签页中的 资源 中的 清单 修改为新建的 app.manifest。
重新生成项目,再次打开程序时就会提示 需要以管理员权限运行。
需要注意的是:如果在VS中 启动调试 的话,就会提示 此任务要求应用程序具有提升的权限。如下图:
选择 使用其他凭据重新启动 即可。
方法三:直接修改程序文件的属性
右击程序文件,在弹出的属性对话框中的 兼容性 标签页中
勾选“以管理员身份运行此程序”即可。
免费注册苹果开发者账号图文教程详解:
1.点击进入苹果开发者社区:http://developer.apple.com
2.然后把页面拉到底部,点击右下角的“Register”
3.跳转页面如下:
如果你已经拥有一个属于自己苹果ID,那么你便可以直接点击“Sign in”直接进入登陆页面(如下图所示);如果你还没拥有自己的苹果ID,那先点击下方的“Create Apple ID ”创建一个吧。
4.点击“Sign in”后跳转至如下页面,然后输入你的苹果ID和密码后,继续点击 “sign in”!
5.接下来就出现了常见的注册注意事项及免责啥啥啥的…就不多说了,按照一贯的惯例,打钩,然后点击同意,看图:
6.接着便是到了个人开发者信息填写页面。填写完毕后,继续点击“Register”,如下图:
7.看到结束页面后,说明此时此刻你的苹果ID同时也是开发者ID了!
如果你还是不太敢相信自己已经成功拥有了一个苹果开发者账号的话,那么,接下来你还可以这样做:
1.打开你的iphone,进入apple store,然后搜索WWDC ,然后把它下载回来吧。
2.下载后打开软件,在下面导航栏里点击maps,然后,再单击一下“Sign in”吧。
此时会要求你登入ID,并提示只有开发者ID才可以登入,那么你就可以点击“sign in ”的文字登入你的ID了,如下图所示:
如果成功,你便会看到WWDC2014大会的各楼层平面规划图,此时你大可放心了,因为你的苹果ID也是开发者ID了!
zend studio这工具会突然抽风,所有函数方法不能自动提示。下面是一些penngo用过的、收集整理的解决方法。
方法1、在不提示的项目上鼠标右键,打开菜单,选择Build Path->Configure Build Path,在弹出窗口中选择PHP Build Path->Add Folder…,把当前项目添加到build path。
如果函数突然不提示,可以先用这个方法检查build path有没有当前项目。但比较多情况是从外部导入项目(例如svn),会没有build path数据。
方法2、先取消菜单Project->Build Automatically的选中状态,再点击Project->Clean…清理项目,再重新选中Project->Build Automatically。这个方法我用得最多。
方法3、打开zend studion的wordspace目录(如果不知wrodspace目录在哪,可以打开菜单file->switch wordspace->other…查看),接着到这个文件夹,打开文件目录.metadata->.plugins->org.eclipse.core.runtime->.settings。到了.settings目录之后,删除org.eclipse.dltk.ui.prefs文件。然后刷新项目或重启Zend Studio。
现在遇到不提示情况,先用方法1检查,再用方法2检查,最后用方法3检查。。。。。大多数情况下,用方法1和方法2就能解决问题。
授人以鱼,不如授人以渔,今天就分享一个 分析qq加密的过程。
工具:谷歌浏览器自带的的调试工具(在浏览器中按F12呼出)
以下是全过程,历时4个的小时。
提交的时候调用 onFormSubmit
1 2 3 4 5 6 7 8 9 10 11 |
function onFormSubmit(form) { if (form.remember_uin.checked){ return ptui_onLoginEx(form, "qq.com") }else{ var myDate=new Date(); myDate.setFullYear(1971,1,1); pt.cookie.set("ptui_loginuin", "", myDate, '/', 'ui.ptlogin2.qq.com'); return ptui_onLogin(form); } } |
如果用户没有勾选保存密码调用 ptui_onLogin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function ptui_onLogin(A) { try { if (parent.ptlogin2_onLogin) { if (!parent.ptlogin2_onLogin()) { return false } } if (parent.ptlogin2_onLoginEx) { var D = A.u.value; var B = A.verifycode.value; if (str_uintip == D) { D = "" } if (!parent.ptlogin2_onLoginEx(D, B)) { return false } } } catch(C) {} return ptui_checkValidate(A) } |
接下来调用 ptui_checkValidate(A)
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
function ptui_checkValidate(B) { var A = B.u; //此处获取用户名 var D = B.p; //此处获取密码 var E = B.verifycode; //此处获取验证码 if (A.value == "" || str_uintip == A.value) { pt.show_err(str_no_uin); A.focus(); return false } A.value = A.value.trim(); if (!pt.chkUin(A.value)) { pt.show_err(str_inv_uin); A.focus(); A.select(); return false } if (D.value == "") { pt.show_err(str_no_pwd); D.focus(); return false } if (E.value == "") { if (!isLoadVC) { loadVC(true); g_submitting = true; return false } pt.show_err(str_no_vcode); try { E.focus() } catch(C) {} if (!g_loadcheck) { ptui_reportAttr(78028) } else { ptui_reportAttr(78029) } return false } if (E.value.length &lt; 4) { pt.show_err(str_inv_vcode); E.focus(); E.select(); return false } if (isLoadVC &amp;&amp; !(/^[a-zA-Z0-9]+$/.test(E.value))) { pt.show_err(str_correct_vcode); E.focus(); E.select(); return false } D.setAttribute("maxlength", "32"); ajax_Submit(); ptui_reportNum(g_changeNum); g_changeNum = 0; return true } |
然后:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function ajax_Submit() { if (pt.checkRet == -1 || pt.checkRet == 3) { pt.show_err(pt.checkErr[window.g_lang]); try { $("p").focus() } catch(B) {} return } var A = getSubmitUrl("login"); pt.winName.set("login_param", encodeURIComponent(login_param)); pt.loadScript(A); return } |
最后在这个函数中加密组装提交地址:
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
function getSubmitUrl(K) { var E = true; var C = document.forms[0]; var A = (pt.isHttps ? "https://ssl.": "http://") + "ptlogin2." + g_domain + "/" + K + "?"; var B = document.getElementById("login2qq"); if (pt.regmaster == 2) { A = "http://ptlogin2.function.qq.com/" + K + "?regmaster=2&amp;" } else { if (pt.regmaster == 3) { A = "http://ptlogin2.crm2.qq.com/" + K + "?regmaster=3&amp;" } } for (var J = 0; J &lt; C.length; J++) { if (K == "ptqrlogin" &amp;&amp; (C[J].name == "u" || C[J].name == "p" || C[J].name == "verifycode" || C[J].name == "h")) { continue } if (C[J].name == "ipFlag" &amp;&amp; !C[J].checked) { A += C[J].name + "=-1&amp;"; continue } if (C[J].name == "fp" || C[J].type == "submit") { continue } if (C[J].name == "ptredirect") { g_ptredirect = C[J].value } if (C[J].name == "low_login_enable" &amp;&amp; (!C[J].checked)) { E = false; continue } if (C[J].name == "low_login_hour" &amp;&amp; (!E)) { continue } if (C[J].name == "webqq_type" &amp;&amp; !B &amp;&amp; (!C[J].checked)) { continue } A += C[J].name; A += "="; if (C[J].name == "u" &amp;&amp; pt.needAt) { A += pt.needAt + "&amp;"; continue } if (C[J].name == "p") { var M = C.p.value; var I = hexchar2bin(md5(M)); var H = md5(I + pt.uin); var G = md5(H + C.verifycode.value.toUpperCase()); A += G } else { if (C[J].name == "u1" || C[J].name == "ep") { var D = C[J].value; var L = ""; if (g_appid == "1003903" &amp;&amp; B) { L = /\?/g.test(D) ? "&amp;": "?"; var F = document.getElementById("webqq_type").value; L += "login2qq=" + B.value + "&amp;webqq_type=" + F } A += encodeURIComponent(D + L) } else { A += C[J].value } } A += "&amp;" } A += "fp=loginerroralert&amp;action=" + pt.action.join("-") + "-" + (new Date() - g_begTime) + "&amp;mibao_css=" + pt.mibao_css + "&amp;t=" + pt.submitN[pt.uin] + "&amp;g=1"; A += "&amp;js_type=" + pt.js_type + "&amp;js_ver=" + window.g_pt_version + "&amp;login_sig=" + window.g_login_sig; return A } |
核心的加密代码如下:
1 2 3 4 5 6 7 8 |
if (C[J].name == "p") { var M = C.p.value; var I = hexchar2bin(md5(M)); var H = md5(I + pt.uin); //pt.uin 估计是用户qq号的16进制表示 var G = md5(H + C.verifycode.value.toUpperCase()); A += G var M = "××××××";var ver="!";var I = hexchar2bin(md5(M));var H = md5(I + pt.uin);var G = md5(H + ver.toUpperCase()); |
hexchar2bin算法如下:
1 2 3 4 5 6 7 8 9 |
function hexchar2bin(str) { var arr = []; for (var i = 0; i &lt; str.length; i = i + 2) { arr.push("\\x" + str.substr(i, 2)) } arr = arr.join(""); eval("var temp = '" + arr + "'"); return temp } |
最终加密过程如下:
1 2 3 4 5 6 7 8 9 |
md5(md5(hexchar2bin(md5(pwd))+uin)+verifycode.toUpperCase()) #!js md5(md5(hexchar2bin(md5("××××××××"))+'\x00\x00\x00\x00\x01\xd3\xff\xf3')+"!EHZ") "918AAFDF8C9481F7AC2FC1C89A4DED7B" |
此处改变了 pt.uin:
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 |
function ptui_checkVC(A, C, B) { clearTimeout(checkClock); pt.checkRet = A; pt.uin = B; if (A == "2") { g_uin = "0"; pt.show_err(str_inv_uin) } if (!pt.submitN[B]) { pt.submitN[B] = 1 } var E = new Date(); g_time.time7 = E; var D = { "12": g_time.time7 - g_time.time6 }; if (!curXui) { ptui_speedReport(D) } g_loadcheck = false; switch (A + "") { case "0": case "2": case "3": $("verifycode").value = C || "abcd"; loadVC(false); break; case "1": $("verifycode").value = pt.needCodeTip ? str_codetip: ""; loadVC(true); break; default: break } } |
其实找出这个算法花的时间很少,只是一直找不到 ptui_checkVC 调用的地方,后来恍然大悟,在验证qq是否需要图片验证码的时候返回的就是给js调用的,地址是:
1 |
https://ssl.ptlogin2.qq.com/chec ... 5Q4YxDJ8Rza4-1ubGMR*aruR6Byct1dQ&u1=http%3A%2F%2Fweb2.qq.com%2Floginproxy.html&r=0.5011255156714469 |
返回内容如下:
1 |
ptui_checkVC('0','!BGC','\x00\x00\x00\x00\x01\xd3\xff\xf3'); |
第三个参数就是 16进制表示的qq号码
至此全搞定,剩下的就是编程实现。https方式访问。可以试试 libcurl 或者自己 用openssl+socket也可以
各种吐槽:
不告诉你 | 2014/03/27 22:55 | #
前端功力很强啊
Kuuki | 2014/03/28 00:17 | #
记得13年XDCTF线上赛中某题就是重现朋友网加密过程…
刘海哥 | 2014/03/28 09:33 | #
膜拜
PlO | 2014/03/28 10:10 | #
http://blog.csdn.net/agoago_2009/article/details/9492961
园长 | 2014/03/31 11:17 | #
很早以前就有人把webqq的协议和demo发出来了,调用webqq接口可以自由收发消息。
niphor | 2014/04/04 16:32 | #
QQ 新浪微博 什么的登陆都基本一样的流程
本文“2014 最新 web qq 密码的加密方式分析过程,WebQQ 登陆密码加密算法分析”,来自:Nuclear’Atk 网络安全研究中心,本文地址:http://lcx.cc/?i=4272,转载请注明作者及出处!