您的位置:首页 > 编程语言 > PHP开发

用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,就算正确的。这样的验证码的好处是,答案并没有显示出来,问题要经过人脑分析。除非对方建立一个对应的题库,否则电脑是无法破解的。这个好像有点类似于自动机器人。
破解程序如下:

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 "echo "";

// 显示顶部坐标

for( $i = 1; $i <= $this->imageInfo['Weight']; $i++ )

echo "";

echo "";

// 逐行绘制表格

for( $I = 1; $I <= count( $this->imgData ); $I++ )

{

// 获取本行数据

$line = $this->imgData[$I];

// 显示本行数据偏移

echo "
";

// 逐一绘制表格

for ( $i = 0; $i < count( $line ); $i++ )

{

// 获取当前像素点的颜色数值

$data = $line[$i];

// 判断绘制模式

if ( $this->drawMod == 'source' ) // 原始数据模式

echo "";

else // 观察模式

{

// 如果本像素点颜色是校验码颜色 设置本表格颜色为前景色

if ( in_array( $data, $this->codeColor ) )

$color = $this->frontColor;

else // 如果本像素点颜色不是校验码颜色 设置本表格颜色为背景色

$color = $this->backColor;

// 获取当前像素的数据偏移

$pos = ( $I - 1 ) * $this->imageInfo['Weight'] + $i + 1;

// 显示表格

echo "";

}

}

echo "";

}

echo "";

// 显示底部坐标

for( $i = 1; $i <= $this->imageInfo['Weight']; $i++ )

echo "";

echo "";

echo "
" . sprintf( '%02.0f', $i ) . "
" . ( ( $I - 1 ) * $this->imageInfo['Weight'] ) . "" . sprintf( '%02.0f', $I ) . "" . $data . "" . sprintf( '%02.0f', $I ) . "
" . sprintf( '%02.0f', $i ) . "
"; } /** * 识别位图 * @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; } ?>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: