写这个软件,虽然原理是一件比较简单的事情,但是开发过程中遇到不少的问题,由于GOOGLE也搜不出个所以然,有些我自己解决的就发上来,没解决的也供大家学习参考吧。先宣传一下软件,看技术的直接往下走,写这篇东西真的花费了不少时间来总结和布局,希望能给新手一点帮助
软件开发小记:
2.断断续续开发了差不多1个多月,为了更好的可重用性,调整了好几次。
功能和其他说明:
1. 半自动批量查询密保(有无密码均可查询)
2. 半自动批量设置设置密保(为了保证统一性和减低出错率,故3个问题皆为生日)
3. 半自动批量修改密码(前提是该号码拥有密保,而且密保问题必须是3个生日且密保答案输入正确)
4. 软件最新版本发布在http://www.cnblogs.com/mangohappy/archive/2008/11/21/1338092.html。
5. 如需要注册请发邮件联系作者,暂定¥10.00单机授权(支持支付宝/财付通/转账)。免费版不能导出、一次只能导入5个号码。
6. 由于腾讯对号码的常用IP段的限制,因此申请密保和修改密码等操作,可能因为不在常用的IP段而无法操作。
最新版本:V2.0.0.2(v2.0.0.1版有无法自行修改选项,操作时只使用默认的Bug)
技术点一:Dotfuscator加密注意事项
1.程序集引用了2个dll,Happy.Security.dll和Interop.MSXML2.dll,我希望Dotfuscator对程序集进行Control Flow和Rename加密的同时,不对Interop.MSXML2.dll加密
2.如果选中了Rename加密保护方式,必须将程序集.Properties下的Resoureces类Exclude,如下图,否则会造成程序无法运行。
技术点二:新手玩强名称和强名称的Dotfuscator加密
1.打开Visual Studio自带的命令提示,用sn.exe -k xx.snk产生一个密钥,对于sn.exe命令可以用sn.exe /?来查看参数对应什么功能
2.将snk文件拷贝到项目的文件夹下面,在项目的属性选卡中,找到“签名”,然后选择刚才的密钥为强名称密钥文件,并且选中使用强名称保护
3.生成操作完成后,程序集就已经具备了第一重保障——强名称
4.如果我们不希望别人直观地看到我们的源码,使用Dotfuscator专业版进行加密
5.如果程序集没有使用强名称,则直接用Dotfuscator进行Control Flow、Rename以及其他项的保护加密,如程序集使用了强名称,因此我们必须对程序集进行延迟签名
6.对于刚才生成的程序集,我们还是要打开Visual Studio自带的命令提示,输入sn.exe -Vr xxx,其中xxx代表了需要延迟签名的exe或者dll文件
7.利用Dotfusctor进行加密
8.打开命令提示,输入sn.exe -Vu xxx取消延迟签名,再用sn.exe -R yyy.snk进行重新签名,其中yyy.snk是程序集的密钥文件
9.此时程序集已经具备第二重保障——Dotfuscator加密
10.有兴趣的可以发邮件给我交流mangohappy@qq.com,如非必要,请勿加Q,因为我也不是那么有空,基本每时每刻都有几十条信息要回,有心无力,请见谅。
技术点三:操作Web资源的类
第一种方法就是用HttpWebRequest,使用起来比较方便,COOKIE自己可以操控,可以使用代理等等,但是对于HTTPS页面比较有心无力,可能是因为我技术原因,希望高手出来指点下HTTPS使用HttpWebRequest登录的问题,我在这里遇到的问题是,获取回来的cookie永远为空...;
第二种方法就是使用WebBrowser模拟人工操作,方法简单,绝对适合新手上路,相对复杂的实现可能还需要引用Microsoft.mshtml.dll
但是我在其中遇到不少的问题,比如将WebBrowser中的图片捕获并且在PictureBox中显示出来,实际操作过程中,我发现我的机器运作正常,部分其他机器也运作正常,但是有一些机器不能正常运行关于WebBroswer的部分代码,即运行了没有实现预想的效果。可能是dll问题,也可能是其他问题,到现在我也没有解决。
在这里,顺便发几个关于获取Webbrowser的图片以及屏蔽Webbrowser对话框的方法,看下面的代码,如果要操作其他JS函数也可以,自己推敲呵呵

WebBrowserEx
using System;
using System.Drawing;
using System.Windows.Forms;
using mshtml;
namespace Happy
{
public class WebBrowserEx
{
/// <summary>
/// 获取WebBrowser指定的图片
/// </summary>
/// <param name="webBrowser">需要获取图片的WebBrowser</param>
/// <param name="imgID">指定的图片的id(优先查找指定id)</param>
/// <param name="imgSrc">指定的图片的src,支持模糊查询</param>
/// <param name="imgAlt">指定的图片的src, 支持模糊查询</param>
/// <returns></returns>
public static Image GetRegCodePic(ref WebBrowser webBrowser, String imgID, String imgSrc, String imgAlt)
{
HTMLDocument doc = (HTMLDocument)webBrowser.Document.DomDocument;
HTMLBody body = (HTMLBody)doc.body;
IHTMLControlRange rang = (IHTMLControlRange)body.createControlRange();
IHTMLControlElement img;
// 如果没有图片的ID,通过Src或Alt中的关键字来取
if (imgID.Length == 0)
{
Int32 ImgNum = GetPicIndex(ref webBrowser, ref imgSrc, ref imgAlt);
if (ImgNum == -1)
return null;
img = (IHTMLControlElement)webBrowser.Document.Images[ImgNum].DomElement;
}
else
img = (IHTMLControlElement)webBrowser.Document.All[imgID].DomElement;
rang.add(img);
rang.execCommand("Copy", false, null);
Image regImg = Clipboard.GetImage();
Clipboard.Clear();
return regImg;
}
/// <summary>
/// 获取WebBrowser指定图片的索引
/// </summary>
/// <param name="webBrowser">指定的WebBrowser</param>
/// <param name="imgSrc">指定的图片src,支持模糊查询</param>
/// <param name="imgAlt">指定的图片alt,支持模糊查询</param>
/// <returns></returns>
public static Int32 GetPicIndex(ref WebBrowser webBrowser, ref String imgSrc, ref String imgAlt)
{
IHTMLImgElement img;
// 获取所有的Image元素
for (Int32 i = 0; i < webBrowser.Document.Images.Count; i++)
{
img = (IHTMLImgElement)webBrowser.Document.Images[i].DomElement;
if (imgAlt.Length == 0)
{
if (img.src.IndexOf(imgSrc) >= 0)
return i;
}
else
{
if (imgSrc.Length == 0)
{
// 当imgSrc为空时,只匹配imgAlt
if (img.alt.IndexOf(imgAlt) >= 0)
return i;
}
else
{
// 当imgSrc不为空时,匹配imgAlt和imgSrc任意一个
if (img.alt.IndexOf(imgAlt) >= 0 || img.src.IndexOf(imgSrc) >= 0)
return i;
}
}
}
return -1;
}
/// <summary>
/// 在WebBrowser中屏蔽对话框
/// </summary>
/// <param name="webBrowser">指定屏蔽对话框的WebBrowser</param>
public static void DisableMessageBox(ref WebBrowser webBrowser)
{
IHTMLWindow2 htmlWindow = (IHTMLWindow2)webBrowser.Document.Window.DomWindow;
htmlWindow.execScript("function alert(str){return true;}", "javascript");
htmlWindow.execScript("function window.alert(){return true;}", "javascript");
htmlWindow.execScript("function confirm(){return true;}", "javascript");
}
}
}
第三中方法就是使用XmlHttp了,我发现可以非常方便操作https页面,cookie自行处理,方法也比较简单,因此软件的2.0版本都采用了这个来实现了。不过对于操作JS脚本之类的工作,还是需要引用其他组件来完成。
技术点四:软件注册的实现对软件的保护
1.先生成机器码,可以取计算机CPU的编号、硬盘编号、MAC地址其一或者多者组合等作为机器码,如果觉得有必要,甚至还可以利用加密算法对获取的机器码进一步加密
2.对于注册码,使用RSA算法,公钥随软件一起发布,密钥则保存好用于产生注册码。
3.对于机器码的获取,主要是利用WMI查询,对于WMI查询有空可以玩玩Visual Studio自带的wbemtest.exe,链接到root\vimv2,对照网页的的查询方法来自己摸索一番,看哪些类保存那些信息,这样以后自己需要获取什么信息就可以自己来定制了,不用再Google得那么辛苦,再介绍一个玩WMI的工具WQLAnalyzer,原理和wbemtest.exe一样,只不过看到的更加直观而已。
4.对于RAS如何产生签名和验证签名,对于高手,我想不用多少,如果是新手,可以看看我下面贴出来的类。

Rsa签名和验证

/**//// <summary>
/// Rsa签名和验证
/// </summary>
public class RsaSignature

{
protected RSAPKCS1SignatureFormatter m_rsaFormat;
protected RSAPKCS1SignatureDeformatter m_rsaDeformat;
protected RSACryptoServiceProvider m_rsaPv;


/**//// <summary>
/// 构造函数
/// </summary>
/// <param name="xmlString">RSA的密钥</param>
public RsaSignature()

{
m_rsaPv = new RSACryptoServiceProvider();
}


/**//// <summary>
/// 设置RAS的密钥
/// </summary>
/// <param name="xmlString">密钥的XML字符串</param>
public void FromXmlString(String xmlString)

{
m_rsaPv.FromXmlString(xmlString);
}


/**//// <summary>
/// 获取RAS的密钥
/// </summary>
/// <param name="includePrivateParameters">是否私钥</param>
public void ToXmlString(Boolean includePrivateParameters)

{
m_rsaPv.ToXmlString(includePrivateParameters);
}


/**//// <summary>
/// 产生签名数据
/// </summary>
/// <param name="userData">原始数据</param>
/// <param name="hashAlgorithm">散列算法(SHA1/MD5)</param>
/// <returns></returns>
public String CreateSignature(String userData, String hashAlgorithm)

{
m_rsaFormat = new RSAPKCS1SignatureFormatter(m_rsaPv);
Byte[] hashData;

switch (hashAlgorithm.ToUpper())

{
case "SHA1":
m_rsaFormat.SetHashAlgorithm("SHA1");
SHA1CryptoServiceProvider sha1Pv = new SHA1CryptoServiceProvider();
hashData = sha1Pv.ComputeHash(Encoding.Unicode.GetBytes(userData));
break;
case "MD5":
m_rsaFormat.SetHashAlgorithm("MD5");
MD5CryptoServiceProvider md5Pv = new MD5CryptoServiceProvider();
hashData = md5Pv.ComputeHash(Encoding.Unicode.GetBytes(userData));
break;
default:
throw new ArgumentNullException("指定的散列算法错误,不支持此散列算法");
}

return Convert.ToBase64String(m_rsaFormat.CreateSignature(hashData));
}


/**//// <summary>
/// 验证签名数据
/// </summary>
/// <param name="userData">原始数据</param>
/// <param name="signData">签名数据</param>
/// <param name="hashAlgorithm">散列算法(SHA1/MD5)</param>
/// <returns></returns>
public Boolean VerifySignature(String userData, String signData, String hashAlgorithm)

{
m_rsaDeformat = new RSAPKCS1SignatureDeformatter(m_rsaPv);
Byte[] hashData;

switch (hashAlgorithm.ToUpper())

{
case "SHA1":
m_rsaDeformat.SetHashAlgorithm("SHA1");
SHA1CryptoServiceProvider sha1Pv = new SHA1CryptoServiceProvider();
hashData = sha1Pv.ComputeHash(Encoding.Unicode.GetBytes(userData));
break;
case "MD5":
m_rsaDeformat.SetHashAlgorithm("MD5");
MD5CryptoServiceProvider md5Pv = new MD5CryptoServiceProvider();
hashData = md5Pv.ComputeHash(Encoding.Unicode.GetBytes(userData));
break;
default:
throw new ArgumentNullException("指定的散列算法错误,不支持此散列算法");
}

return m_rsaDeformat.VerifySignature(hashData, Convert.FromBase64String(signData));
}
}
其他感想
1.为了写这个软件,对腾讯的DNA系统研究了甚久,腾讯限制了QQ号码非常用IP段不能进行一些敏感操作,我用了很多方法都不能绕过去,说明腾讯DNA系统的几个网页上的验证已经考虑得相当周全
2.设置密保的时候,腾讯要求确认一下之前输入的问题,其他在编程的时候这一步可以绕过的,即提交了密保数据,再提交一次最后页面的表单就可以结束密保申请。
3.发现腾讯的JS写得非常复杂,为了研究一个方法,得绕好大一个弯,不知道腾讯的开发人员怎么写的,估计换了人就看不懂。