互動程序
MySQL客戶端與服務器的互動主要分為兩個階段:握手認證階段和命令執行階段,
握手認證階段
握手認證階段為客戶端與服務器建立連接后進行,互動程序如下:
- 服務器 -> 客戶端:握手初始化訊息
- 客戶端 -> 服務器:登陸認證訊息
- 服務器 -> 客戶端:認證結果訊息
命令執行階段
客戶端認證成功后,會進入命令執行階段,互動程序如下:
- 客戶端 -> 服務器:執行命令訊息
- 服務器 -> 客戶端:命令執行結果

上面就是mysql客戶端和服務端的互動流程,然后結合實際中的抓包工具來看先這個程序,這里使用php的PDO擴展連接資料庫并執行一條查詢陳述句,抓包情況如下

下面一條一條的來分析每個包中的內容,在此之前先看下報文的結構,報文分為訊息頭和訊息體兩部分,其中訊息頭占用固定的4個位元組,訊息體長度由訊息頭中的長度欄位決定,報文結構如下:

先看下握手初始化的報文,如圖:

其中前三個位元組 4a 00 00 表示訊息體的長度,但是這里需要注意的是在報文中整數是以小端存盤(即低位放在低地址,高位放在高地址)的方式進行傳輸的,所以轉為我們平時閱讀的形式的話應該是 00 00 4a 轉為十進制應該是 74 ,也就是報文中的訊息體長度應該是 74個位元組(即出去開頭的 4a 00 00 00 位元組訊息頭之外所有的藍色背景部分),再來看下初始化資訊中包含了哪些內容

訊息體中的第一個位元組表示的是協議版本號 0a 轉為十進制是 10 所以協議版本號就是10,16進制整數轉為10進制的實作如下
<?php
function hexToInt($hex_string){
$z = implode("", array_reverse(str_split($hex_string,2)));
return hexdec($z);
}
$str = "4a0000";
echo hexToInt($str); //輸出 74
后面的7個位元組表示服務器的版本號,這里使用 php轉化
<?php
function hexToStr($hex_string){
$result = "";
$hex_array = str_split($hex_string,2);
foreach ($hex_array as $item){
$result .= chr(hexdec($item));
}
return $result;
}
$str = "352e372e313600";
echo hexToStr($str); //輸出 5.7.16
這里特別記錄兩個引數 8位挑戰亂數 和 12位挑戰亂數 這兩個引數用來認證用戶時密碼加密的時候使用,將握手初始化報文組裝成物件
HandleShake.php
<?php
/**
* 服務初始化握手
* Class HandleShake
*
* @author gphper 20200909
* @package PHPMysql\MysqlPacket
*/
class HandleShake
{
private $hex_string;
private $protocol_version;
private $server_version;
private $thread_id;
private $salt1;
private $salt2;
public function __construct($hex_string)
{
$this->hex_string = $hex_string;
$this->setProtocolVersion();
$this->setServerVersion();
$this->setThreadId();
$this->setSalt1();
$this->setSalt2();
}
public function setProtocolVersion(){
$this->protocol_version = UtiliHelper::HexToInt(UtiliHelper::HexSub($this->hex_string,0,1));
}
public function setServerVersion(){
$this->server_version = UtiliHelper::HexToStr(UtiliHelper::HexSub($this->hex_string,1,7));
}
public function setThreadId(){
$this->thread_id = UtiliHelper::HexToInt(UtiliHelper::HexSub($this->hex_string,8,4));
}
public function setSalt1(){
$this->salt1 = UtiliHelper::HexSub($this->hex_string,12,8);
}
public function setSalt2(){
$this->salt2 = UtiliHelper::HexSub($this->hex_string,39,12);
}
public function getSalt(){
return $this->salt1.$this->salt2;
}
}
UtilHelper.php
<?php
class UtiliHelper
{
/**
* 將十六進制轉為字串
* @author gphper 20200908
* @param $hex_str
* @return string
*/
public static function HexToStr($hex_str){
$send_msg = "";
foreach (str_split($hex_str,2) as $key => $value) {
$send_msg .= chr(hexdec($value));
}
return $send_msg;
}
/**
* 十六進制轉整數
* @author gphper 20200908
* @param $hex_string
* @return float|int
*/
public static function HexToInt($hex_string){
$z = implode("", array_reverse(str_split($hex_string,2)));
return hexdec($z);
}
/**
* 截取十六進制
* @author gphper 20200908
* @param $hex_str
* @param $start
* @param $length
* @return bool|string
*/
public static function HexSub($hex_str,$start,$length=0){
if($length){
return substr($hex_str, $start*2,$length*2);
}
return substr($hex_str, $start*2);
}
/**
* 將十六進制訊息體分段
* @author gphper 20200908
* @param $hex_str
* @param int $is_body
* @param array $all
* @return array
*/
public static function HexSplit($hex_str,$is_body=0,$all=[]){
//先獲取前三位
$length = self::HexToInt(self::HexSub($hex_str,0,3));
$total_length = $length + 4;
$start = $is_body*8;
$pre_str = substr($hex_str,$start,$total_length*2-$start);
$sub_str = substr($hex_str,$total_length*2);
$all = array_merge($all,array($pre_str));
if($sub_str){
return self::HexSplit($sub_str,$is_body,$all);
}
return $all;
}
/**
* 十進制轉為十六進制 小端存盤
* @author gphper 20200908
* @param $length
* @return string
*/
public static function IntToHex($length){
$big = str_pad(dechex($length),6,0,STR_PAD_LEFT);
return implode("",array_reverse(str_split($big,2)));
}
/**
* 字符轉十六進制
* @author gphper 20200908
* @param $string
* @return string
*/
public static function StrToHex($string){
$length = strlen($string);
$hex = "";
for ($i = 0; $i<$length; $i++){
$hex .= str_pad(dechex(ord($string[$i])),2,0,STR_PAD_LEFT);
}
return $hex;
}
/**
* 使用回傳服務端回傳的資訊加密密碼
* $seed = "39011e567b3878441a0a560d52083e25336e3c34"
* @author gphper 20200909
* @param string $pass
* @param string $seed
* @return string
*/
public static function scramble411($pass, $seed)
{
$pass1 = self::getBytes(sha1($pass, true));
$pass2 = sha1(self::getString($pass1), true);
$pass3 = self::getBytes(sha1(self::HexToStr($seed) . $pass2, true));
$result = "";
for ($i = 0, $count = count($pass3); $i < $count; ++$i) {
$result .= str_pad(dechex(($pass3[$i] ^ $pass1[$i])),2,0,STR_PAD_LEFT);
}
return $result;
}
public static function getBytes($data)
{
$bytes = [];
$count = strlen($data);
for ($i = 0; $i < $count; ++$i) {
$byte = ord($data[$i]);
$bytes[] = $byte;
}
return $bytes;
}
public static function getString(array $bytes)
{
return implode(array_map('chr', $bytes));
}
}
index.php
<?php
//創建tcp套接字
$socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
//連接tcp
socket_connect($socket, '127.0.0.1',3306);
//初始化握手
$msg = socket_read($socket,8190,PHP_BINARY_READ);
$body_arr = UtiliHelper::HexSplit(bin2hex($msg),1);
$handle_shark = new HandleShake($body_arr[0]);
//關閉連接
socket_close($socket);
生成的 HandleShake 物件
class PHPMysql\MysqlPacket\HandleShake#3 (6) { private $hex_string => string(148) "0a352e372e31360024000000232e200b147b397f00fff7080200ff811500000000000000000000735c762246383411280b5b73006d7973716c5f6e61746976655f70617373776f726400" private $protocol_version => int(10) private $server_version => string(7) "5.7.16\000" private $thread_id => int(36) private $salt1 => string(16) "232e200b147b397f" private $salt2 => string(24) "735c762246383411280b5b73" }
代碼分享地址
https://github.com/gphper/PHPMysql
參考文章:
https://www.cnblogs.com/davygeek/p/5647175.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/29090.html
標籤:PHP
