用php写了个简单的验证码破解程序
2009-03-10 15:03
567 查看
Author:David |
English Version 【转载时请务必以超链接形式标明文章原始出处和作者信息及本声明】
URL:http://blog.iyi.cn/start/2006/04/php_5.html
今天跟公司的同事讨论一个网上投票被刷票的问题,我说验证码太简单了,同事觉得已经很不错了,于是就试着写了个破解程序。
改验证码的图片是下面这个样子的:
四位纯数字的jpg图像,纵坐标稍有变动,横坐标完全不变。背景颜色稍有变化,都是纯色,没有干扰,文字基本上是黑色,不是纯黑色。
jpg的图像,生成的不是很清晰,有些钝化的感觉,所以文字和周围的颜色有些模糊,只要将图片转换为基于调色板的256色bmp图片(应该有更简单的方法,我在网上找到一个现成的分析bmp图片点阵的程序,所以就转换成bmp了),就可以准确提取了。
从破解中可以看出,简单的破解是基于点阵匹配的,只要对字形作一定的扭曲就很难匹配了。背景色的干扰,效果甚微,因为只要能够强烈的对比出文字,就说明RGB三原色中的一种有较强的反差,只要将通道转换为这种颜色,就基本上可以把文字提取出来。
不过道高一尺,魔高一丈,连gmail、hotmail的验证码都很多破解了,qq的验证码被破的不得已,用了中文,8位长度的中文,输入都要半天,而且连人都快看不清了,真服了qq的程序员,但还是被破- -。
其实验证码本质上还是,把内容原文返回给服务器,按照现有的模式识别的水平,人眼可以轻松辨认的文字,机器几乎都可以辨认。也就是说,机器不能辨认,那么人也很难辨认了。
只要加入点人工智能的障碍,验证码破解起来就不那么容易了。可以想象建立一个足够大的题库,问题的答案是要经过人工判断的,多样化的。比如:给出一张图片,上面显示了一个足球,如果用户输入的是足球,或者football就是正确的;再比如:问一个问题,今天是几号?如果用户输入13或者十三,甚至shisan、thirteen,就算正确的。这样的验证码的好处是,答案并没有显示出来,问题要经过人脑分析。除非对方建立一个对应的题库,否则电脑是无法破解的。这个好像有点类似于自动机器人。
破解程序如下:
echo "
";
}
/**
* 识别位图
* @return string 识别后的字串
* @access public
*/
function Valid()
{
// 如果 $this->imgData 不存在 : 获取图片内容
if ( !$this->imgData )
$this->GetData();
// 根据校验码个数逐一识别
for ( $i = 0; $i < $this->codeTotal; $i++)
{
// 计算数字左上数据偏移
$bp = $this->offSet + ( $this->fontWeight + $this->fontSpace ) * $i;
// 获取对比特征码
$tmp = $this->_GetBound( $bp );
// 设置当前字符默认识别结果 * 代表无法识别
$c = '*';
// 遍历对比码,检查匹配情况
foreach ( $this->collateCode as $k => $v )
{
// 匹配
if ( $tmp === $v )
{
// 设置当前字符识别结果
$c = $k;
break;
}
}
// 组合结果
$rs .= $c;
}
return $rs;
}
/**
* 初始化数据状态
* @access public
*/
function Init()
{
$this->imageInfo = array();
$this->buffer = array();
$this->imgData = array();
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// private function
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* 读取INT
* @return integer 数字
* @access private
*/
function _ReadInt()
{
return $this->_Bin2asc( fread ( $this->fp, 4 ) );
}
/**
* 二进制转ASCII
* @return string 转换后的内容
* @access private
*/
function _Bin2asc ( $binary )
{
$val = 0;
for ($i = strlen( $binary ) - 1; $i >= 0; $i--)
{
$ch = substr( $binary, $i, 1 );
$val = ( $val << 8 ) | ord( $ch );
}
return $val;
}
/**
* 获取数据区域特征码
* @return string 特征码
* @access private
*/
function _GetBound( $bp )
{
// 移动指针
fseek( $this->fp, $bp );
// 按照矩形图形从左到右、从上到下的方向进行提取
for ( $i = 1; $i <= $this->fontHeight; $i++ )
{
for ( $ii = 1; $ii <= $this->fontWeight; $ii++ )
{
// 获取当前像素点颜色数值
$data = $this->_Bin2asc( fread( $this->fp, 1 ) );
// 如该像素颜色数值和校验码颜色匹配则表示为1, 否则为0
if ( in_array( $data, $this->codeColor ) )
$rs .= 1;
else
$rs .= 0;
}
// 指针移动到下一校验码区域
fseek( $this->fp, $bp - $i * $this->imageInfo['Weight'] );
}
return $rs;
}
}
function imagebmp(&$im, $filename = '', $bit = 8, $compression = 0)
{
if (!in_array($bit, array(1, 4, 8, 16, 24, 32)))
{
$bit = 8;
}
else if ($bit == 32) // todo:32 bit
{
$bit = 24;
}
$bits = pow(2, $bit);
// 调整调色板
imagetruecolortopalette($im, true, $bits);
$width = imagesx($im);
$height = imagesy($im);
$colors_num = imagecolorstotal($im);
if ($bit <= 8)
{
// 颜色索引
$rgb_quad = '';
for ($i = 0; $i < $colors_num; $i ++)
{
$colors = imagecolorsforindex($im, $i);
$rgb_quad .= chr($colors['blue']) . chr($colors['green']) . chr($colors['red']) . "/0";
}
// 位图数据
$bmp_data = '';
// 非压缩
if ($compression == 0 || $bit < 8)
{
if (!in_array($bit, array(1, 4, 8)))
{
$bit = 8;
}
$compression = 0;
// 每行字节数必须为4的倍数,补齐。
$extra = '';
$padding = 4 - ceil($width / (8 / $bit)) % 4;
if ($padding % 4 != 0)
{
$extra = str_repeat("/0", $padding);
}
for ($j = $height - 1; $j >= 0; $j --)
{
$i = 0;
while ($i < $width)
{
$bin = 0;
$limit = $width - $i < 8 / $bit ? (8 / $bit - $width + $i) * $bit : 0;
for ($k = 8 - $bit; $k >= $limit; $k -= $bit)
{
$index = imagecolorat($im, $i, $j);
$bin |= $index << $k;
$i ++;
}
$bmp_data .= chr($bin);
}
$bmp_data .= $extra;
}
}
// RLE8 压缩
else if ($compression == 1 && $bit == 8)
{
for ($j = $height - 1; $j >= 0; $j --)
{
$last_index = "/0";
$same_num = 0;
for ($i = 0; $i <= $width; $i ++)
{
$index = imagecolorat($im, $i, $j);
if ($index !== $last_index || $same_num > 255)
{
if ($same_num != 0)
{
$bmp_data .= chr($same_num) . chr($last_index);
}
$last_index = $index;
$same_num = 1;
}
else
{
$same_num ++;
}
}
$bmp_data .= "/0/0";
}
$bmp_data .= "/0/1";
}
$size_quad = strlen($rgb_quad);
$size_data = strlen($bmp_data);
}
else
{
// 每行字节数必须为4的倍数,补齐。
$extra = '';
$padding = 4 - ($width * ($bit / 8)) % 4;
if ($padding % 4 != 0)
{
$extra = str_repeat("/0", $padding);
}
// 位图数据
$bmp_data = '';
for ($j = $height - 1; $j >= 0; $j --)
{
for ($i = 0; $i < $width; $i ++)
{
$index = imagecolorat($im, $i, $j);
$colors = imagecolorsforindex($im, $index);
if ($bit == 16)
{
$bin = 0 << $bit;
$bin |= ($colors['red'] >> 3) << 10;
$bin |= ($colors['green'] >> 3) << 5;
$bin |= $colors['blue'] >> 3;
$bmp_data .= pack("v", $bin);
}
else
{
$bmp_data .= pack("c*", $colors['blue'], $colors['green'], $colors['red']);
}
// todo: 32bit;
}
$bmp_data .= $extra;
}
$size_quad = 0;
$size_data = strlen($bmp_data);
$colors_num = 0;
}
// 位图文件头
$file_header = "BM" . pack("V3", 54 + $size_quad + $size_data, 0, 54 + $size_quad);
// 位图信息头
$info_header = pack("V3v2V*", 0x28, $width, $height, 1, $bit, $compression, $size_data, 0, 0, $colors_num, 0);
// 写入文件
if ($filename != '')
{
$fp = fopen("test.bmp", "wb");
fwrite($fp, $file_header);
fwrite($fp, $info_header);
fwrite($fp, $rgb_quad);
fwrite($fp, $bmp_data);
fclose($fp);
return 1;
}
// 浏览器输出
header("Content-Type: image/bmp");
echo $file_header . $info_header;
echo $rgb_quad;
echo $bmp_data;
return 1;
}
?>
English Version 【转载时请务必以超链接形式标明文章原始出处和作者信息及本声明】
URL:http://blog.iyi.cn/start/2006/04/php_5.html
今天跟公司的同事讨论一个网上投票被刷票的问题,我说验证码太简单了,同事觉得已经很不错了,于是就试着写了个破解程序。
改验证码的图片是下面这个样子的:
四位纯数字的jpg图像,纵坐标稍有变动,横坐标完全不变。背景颜色稍有变化,都是纯色,没有干扰,文字基本上是黑色,不是纯黑色。
jpg的图像,生成的不是很清晰,有些钝化的感觉,所以文字和周围的颜色有些模糊,只要将图片转换为基于调色板的256色bmp图片(应该有更简单的方法,我在网上找到一个现成的分析bmp图片点阵的程序,所以就转换成bmp了),就可以准确提取了。
从破解中可以看出,简单的破解是基于点阵匹配的,只要对字形作一定的扭曲就很难匹配了。背景色的干扰,效果甚微,因为只要能够强烈的对比出文字,就说明RGB三原色中的一种有较强的反差,只要将通道转换为这种颜色,就基本上可以把文字提取出来。
不过道高一尺,魔高一丈,连gmail、hotmail的验证码都很多破解了,qq的验证码被破的不得已,用了中文,8位长度的中文,输入都要半天,而且连人都快看不清了,真服了qq的程序员,但还是被破- -。
其实验证码本质上还是,把内容原文返回给服务器,按照现有的模式识别的水平,人眼可以轻松辨认的文字,机器几乎都可以辨认。也就是说,机器不能辨认,那么人也很难辨认了。
只要加入点人工智能的障碍,验证码破解起来就不那么容易了。可以想象建立一个足够大的题库,问题的答案是要经过人工判断的,多样化的。比如:给出一张图片,上面显示了一个足球,如果用户输入的是足球,或者football就是正确的;再比如:问一个问题,今天是几号?如果用户输入13或者十三,甚至shisan、thirteen,就算正确的。这样的验证码的好处是,答案并没有显示出来,问题要经过人脑分析。除非对方建立一个对应的题库,否则电脑是无法破解的。这个好像有点类似于自动机器人。
破解程序如下:
print(" 的破解结果是:"); $bmp = new NEATBMP256ValidPic; $bmp->SetFile( 'test.bmp' ); $bmp->Open(); $str = $bmp->decode(13,$arrayCode); print $str; $str = $bmp->decode(33,$arrayCode); print $str; $str = $bmp->decode(53,$arrayCode); print $str; $str = $bmp->decode(73,$arrayCode); print $str; ?> decode.php:这是别人写的一个强大的bmp处理类,我没有整理,只是加了两个定制的函数,来破解我的图片。printCode和decode。另外,最后面一个函数imagebmp,是从网上找来的,生成bmp图片的函数。因为我不知道如何简化jpg图像和处理点阵。 /* * 本代码思路基于 NetDust 的文章 “程序识别验证码图片” . * 由NEATSTUDIO ( http://www.neatstudio.com ) 的 walkerlee 用PHP重新描述, 并改进成半通用版本. * 本代码片段遵循GPL许可协议发布. 您可以任意修改和传播本代码片段,但请保留我们的版权信息. */ /** * 256色BMP图片校验码识别类 * Created / Modify : 2005-8-21 / 2005-8-22 * @name NEATBMP256ValidPic * @version 1.0.0 * @author walkerlee * @copyright Powered by NEATSTUDIO 2002 - 2005 * @link http://www.neatstudio.com NeatStudio * @package NEATFramework * @subpackage NEATImage */ /** * TODO : * 异常处理,比如,在处理前先判断图片是否是BMP格式。并且是256色。 */ class NEATBMP256ValidPic { /** * 待处理文件的绝对或者相对路径 * @var string * @access private */ var $bmpFile = ''; /** * 前景色 * @var string * @access private */ var $frontColor = '#3399CC'; /** * 背景色 * @var string * @access private */ var $backColor = '#FFFFFF'; /** * 验证码颜色 * @var integer * @access private */ var $codeColor = ''; /** * 表格绘图模式 * @var string * @access private */ var $drawMod = ''; /** * 字体宽度 * @var integer * @access private */ var $fontWeight = ''; /** * 字体高度 * @var integer * @access private */ var $fontHeight = ''; /** * 字间距离 * @var integer * @access private */ var $fontSpace = ''; /** * 待识别验证码字符个数 * @var integer * @access private */ var $codeTotal = ''; /** * 第一个数字的左上角的数据偏移 * @var integer * @access private */ var $offSet = ''; /** * 对比码数组 * @var array * @access private */ var $collateCode = array(); ////////////////////////////////////////////////////////////////////////////// /** * 图片信息数组 * @var array * @access private */ var $imageInfo = array(); /** * 原始图像数据数组 * @var array * @access private */ var $buffer = array(); /** * 图片数据数组 * @var array * @access private */ var $imgData = array(); ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // communications function ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * 设置待处理的文件 * @param string $file 文件绝对或者相对路径 * @access public */ function SetFile( $file ) { $this->bmpFile = $file; } /** * 设置前景色 * @param string $color 前景色 用于在表格上显示 16位格式 例:#FF0000 * @access public */ function SetFrontColor( $color ) { $this->frontColor = $color; } /** * 设置背景色 * @param string $color 背景色 用于在表格上显示 16位格式 例:#FF0000 * @access public */ function SetBackColor( $color ) { $this->backColor = $color; } /** * 设置验证码颜色 * @param string $color 验证码颜色 验证码的着色 范围在0-255 * @access public */ function SetCodeColor( $color ) { $this->codeColor = $color; } /** * 设置表格绘图模式 * @param string $mod 绘图模式 source 是直接显示出当前像素的颜色代码 0-255 * @access public */ function SetDrawMod( $mod ) { $this->drawMod = $mod; } /** * 设置字体宽度 * @param integer $weight 字体宽度 以像素为单位 * @access public */ function SetFontWeight( $weight ) { $this->fontWeight = $weight; } /** * 设置字体高度 * @param integer $height 字体高度 以像素为单位 * @access public */ function SetFontHeight( $height ) { $this->fontHeight = $height; } /** * 设置字间距离 * @param integer $space 字间距离 以像素为单位 * @access public */ function SetFontSpace( $space ) { $this->fontSpace = $space; } /** * 设置待识别的验证码字符个数 * @param integer $total 字符个数 * @access public */ function SetCodeTotal( $total ) { $this->codeTotal = $total; } /** * 设置第一个数字的左上角的数据偏移 * @param integer $offset 偏移位置 * @access public */ function SetOffSet( $offset ) { $this->offSet = $offset; } /** * 设置对比码 * @param array $code 对比码数组 * @access public */ function SetCollateCode( $code ) { $this->collateCode = $code; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // common operation function ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * 打开文件 * @return boolean * @access public */ function Open() { return $this->fp = fopen( $this->bmpFile, "rb" ); } /** * 关闭文件 * @return boolean * @access public */ function Close() { return fclose( $this->fp ); } /** * 读取配置参数 * @param array $config 配置参数数组 * @access public */ function LoadConfig( $config ) { $this->codeColor = $config['CodeColor']; $this->codeTotal = $config['CodeTotal' ]; $this->fontWeight = $config['FontWeight']; $this->fontHeight = $config['FontHeight']; $this->fontSpace = $config['FontSpace']; $this->offSet = $config['OffSet']; $this->collateCode = $config['CollateCode']; } /** * 返回已经获取的图片信息 * @return array 图片信息数组 * @access public */ function GetInfo() { return $this->imageInfo; } /** * 获取原始的图片内容 * @return array 原始图片内容数组 * @access public */ function GetBaseData() { // 如果 $this->imageInfo 不存在 : 读取图片信息 if ( !$this->imageInfo ) $this->Info(); // 逐行读取 for ( $i = $this->imageInfo['Height'] - 1; $i >= 0; $i-- ) { // 计算出数据偏移 $pos = $this->imageInfo['Offset'] + ( $this->imageInfo['Height'] - $i - 1 ) * $this->imageInfo['Weight']; // 移动指针 fseek ( $this->fp, $pos ); // 计算本行数据偏移 $line = $i * $this->imageInfo['Weight']; // 初始化数组 $arr = array(); // 逐列读取 for ( $k = 0; $k < $this->imageInfo['Weight'] ; $k++ ) { // 读取当前像素点的颜色数值,保存进本列数组 $arr[] = $this->_Bin2asc( fread( $this->fp, 1 ) ); } // 构造数组 $this->buffer[$line] = $arr; } return $this->buffer; } /** * 获取图片内容 * @param string $order 参数:shift 原始存储模式 图片是反着存储的。用于寻找第一个字符的数据偏移 参数:pop 显示模式 用于显示。 * @return array 图片内容数组 * @see GetBaseData * @access public */ function GetData( $order = 'shift' ) { // 如果 $this->buffer 不存在 : 获取图片原始内容 if ( !$this->buffer ) $this->GetBaseData(); // 判断数据构造模式 if ( $order == 'shift' ) $handleMod = 'array_shift'; // 存储模式 图像倒置 else $handleMod = 'array_pop'; // 显示模式 图像正置 // 获取原始图片内容数组的行数 $arrayNum = count( $this->buffer ); // 逐行处理 for( $i = 1; $i <= $arrayNum; $i++ ) { // 重建数组 $this->imgData[$i] = $handleMod( $this->buffer ); } return $this->imgData; } /** * 取得图片信息 * @return array 图片信息数组 * @access public */ function Info() { // 读取图片格式 $this->imageInfo['Type'] = fread( $this->fp, 2 ); // 读取图片尺寸 $this->imageInfo['Size'] = $this->_ReadInt(); // 保留地址 $this->imageInfo['Reserved'] = $this->_ReadInt(); // 获取数据偏移 $this->imageInfo['Offset'] = $this->_ReadInt(); // 获取头信息 $this->imageInfo['Header'] = dechex( $this->_ReadInt() ) . 'H'; // 获取图片的宽度 $this->imageInfo['Weight'] = $this->_ReadInt(); // 获取图片的高度 $this->imageInfo['Height'] = $this->_ReadInt(); // 获取图片的位面数 $this->imageInfo['Planes'] = $this->_Bin2asc( fread( $this->fp, 2 ) ); // 获取图片的每个象素的位数 $this->imageInfo['Bits Per Pixel'] = $this->_Bin2asc( fread( $this->fp, 2 ) ); // 获取图片的压缩说明 $this->imageInfo['Compression'] = $this->_ReadInt(); // 用字节数表示的位图数据的大小。该数必须是4的倍数 $this->imageInfo['Bitmap Data Size'] = $this->_ReadInt(); // 用象素/米表示的水平分辨率 $this->imageInfo['HResolution'] = $this->_ReadInt(); // 用象素/米表示的垂直分辨率 $this->imageInfo['VResolution'] = $this->_ReadInt(); // 位图使用的颜色数。如8-比特/象素表示为100h或者 256. $this->imageInfo['Colors'] = $this->_ReadInt(); // 指定重要的颜色数。当该域的值等于颜色数时(或者等于0时),表示所有颜色都一样重要 $this->imageInfo['Important Colors'] = $this->_ReadInt(); } function printCode($left){ if ( !$this->imgData ) $this->GetData(); $line = $this->imgData[1]; $row = count( $this->imgData ); for ($Y = 1; $Y <= count($line); $Y++){ for( $X = $left; $X < ($left + 6); $X++ ){ if( $this->imgData[$Y][$X] == 0){ print($Y); for($y = $Y; $y < $Y+10; $y++){ print(" "); for($x = $left; $x < ($left + 6); $x++){ print($this->imgData[$y][$x]); } } break 2; } } } } function decode($left,$array){ if ( !$this->imgData ) $this->GetData(); $code = ""; $line = $this->imgData[1]; $row = count( $this->imgData ); for ($Y = 1; $Y <= count($line); $Y++){ for( $X = $left; $X < ($left + 6); $X++ ){ if( $this->imgData[$Y][$X] == 0){ for($y = $Y; $y < $Y+10; $y++){ for($x = $left; $x < ($left + 6); $x++){ $code = $code.(string)$this->imgData[$y][$x]; } } break 2; } } } foreach ( $array as $k => $v ) { // 匹配 if ( $code === $v ) { // 设置当前字符识别结果 $c = $k; break; } } return $c; } /** * 绘出坐标表 * @access public */ function Draw() { // 如果 $this->imgData 不存在 : 获取图片内容 if ( !$this->imgData ) $this->GetData(); // 显示表格以及样式 echo "
"; // 显示顶部坐标 for( $i = 1; $i <= $this->imageInfo['Weight']; $i++ ) echo " | " . sprintf( '%02.0f', $i ) . " | "; echo "|||
" . ( ( $I - 1 ) * $this->imageInfo['Weight'] ) . " | " . sprintf( '%02.0f', $I ) . " | "; // 逐一绘制表格 for ( $i = 0; $i < count( $line ); $i++ ) { // 获取当前像素点的颜色数值 $data = $line[$i]; // 判断绘制模式 if ( $this->drawMod == 'source' ) // 原始数据模式 echo "" . $data . " | "; else // 观察模式 { // 如果本像素点颜色是校验码颜色 设置本表格颜色为前景色 if ( in_array( $data, $this->codeColor ) ) $color = $this->frontColor; else // 如果本像素点颜色不是校验码颜色 设置本表格颜色为背景色 $color = $this->backColor; // 获取当前像素的数据偏移 $pos = ( $I - 1 ) * $this->imageInfo['Weight'] + $i + 1; // 显示表格 echo ""; } } echo " | " . sprintf( '%02.0f', $I ) . " |
"; // 显示底部坐标 for( $i = 1; $i <= $this->imageInfo['Weight']; $i++ ) echo " | " . sprintf( '%02.0f', $i ) . " | "; echo "
相关文章推荐
- php 自己写的简单验证码程序
- PHP+AJAX实现简单验证码程序实例
- 简单验证码破解程序
- 分享一个php验证码程序 简单 实用
- 蜘蛛爬虫类程序抓取有防盗链的网站处理 php和wget命令简单破解防盗链网站的功能
- 分享一个php验证码程序 简单 实用
- 一个简单php版暴力破解程序
- 一个简单的php图形验证码生成程序
- 一个简单的php图形验证码生成程序
- linux实践——简单程序破解
- 用imagemagick和tesseract-ocr破解简单验证码
- 发一个php简单的伪原创程序,配合商城采集用的
- windbg 基础命令实战 - 简单程序破解
- 发一个php简单的伪原创程序,配合商城采集用的
- 验证码破解的方法,简单验证码破解演示(转)
- php网页游戏开发教程三(简单程序应用)
- 解析:通过php socket并借助telnet实现简单的聊天程序
- 支持中文字母数字、自定义字体php验证码程序
- 两个简单的画验证码图形程序
- 第一个简单的用户注册验证php程序