From d8a61db7ace2cb0a96999272f03e0a684c3bbfa9 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 12 Apr 2018 21:46:19 +0200 Subject: [PATCH] Start writing tdesktop conversion --- src/danog/MadelineProto/Conversion.php | 162 +++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/src/danog/MadelineProto/Conversion.php b/src/danog/MadelineProto/Conversion.php index d18aebae..95f5512c 100644 --- a/src/danog/MadelineProto/Conversion.php +++ b/src/danog/MadelineProto/Conversion.php @@ -15,6 +15,56 @@ namespace danog\MadelineProto; class Conversion { + public static function random($length) + { + return $length === 0 ? '' : \phpseclib\Crypt\Random::string($length); + } + + public static function unpack_signed_int($value) + { + if (strlen($value) !== 4) { + throw new TL\Exception(\danog\MadelineProto\Lang::$current_lang['length_not_4']); + } + + return unpack('l', !\danog\MadelineProto\Magic::$BIG_ENDIAN ? strrev($value) : $value)[1]; + } + + public static function pack_signed_int($value) + { + if ($value > 2147483647) { + throw new TL\Exception(sprintf(\danog\MadelineProto\Lang::$current_lang['value_bigger_than_2147483647'], $value)); + } + if ($value < -2147483648) { + throw new TL\Exception(sprintf(\danog\MadelineProto\Lang::$current_lang['value_smaller_than_2147483648'], $value)); + } + $res = pack('l', $value); + + return !\danog\MadelineProto\Magic::$BIG_ENDIAN ? strrev($res) : $res; + } + + + public static function old_aes_calculate($msg_key, $auth_key, $to_server = true) + { + $x = $to_server ? 0 : 8; + $sha1_a = sha1($msg_key.substr($auth_key, $x, 32), true); + $sha1_b = sha1(substr($auth_key, 32 + $x, 16).$msg_key.substr($auth_key, 48 + $x, 16), true); + $sha1_c = sha1(substr($auth_key, 64 + $x, 32).$msg_key, true); + $sha1_d = sha1($msg_key.substr($auth_key, 96 + $x, 32), true); + $aes_key = substr($sha1_a, 0, 8).substr($sha1_b, 8, 12).substr($sha1_c, 4, 12); + $aes_iv = substr($sha1_a, 8, 12).substr($sha1_b, 0, 8).substr($sha1_c, 16, 4).substr($sha1_d, 0, 8); + + return [$aes_key, $aes_iv]; + } + + public static function ige_decrypt($message, $key, $iv) + { + $cipher = new \phpseclib\Crypt\AES('ige'); + $cipher->setKey($key); + $cipher->setIV($iv); + + return @$cipher->decrypt($message); + } + public static function telethon($session, $new_session, $settings = []) { set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']); @@ -78,4 +128,116 @@ class Conversion $MadelineProto->API->init_authorization(); return $MadelineProto; } + + public static function tdesktop_md5($data) { + $result = ''; + foreach (str_split(md5($data), 2) as $byte) { + $result .= strrev($byte); + } + return strtoupper($result); + } + + const FILEOPTION_SAFE = 1; + const FILEOPTION_USER = 2; + public static $tdesktop_base_path; + public static $tdesktop_user_base_path; + public static $tdesktop_key; + public static function tdesktop_fopen($fileName, $options = 3) { + $name = ($options & self::FILEOPTION_USER ? self::$tdesktop_user_base_path : self::$tdesktop_base_path) . $fileName; + $totry = []; + for ($x = 0; $x <= 1; $x++) { + if (file_exists($name.$x)) $totry []= fopen($name.$x, 'rb'); + } + foreach ($totry as $fp) { + if (stream_get_contents($fp, 4) !== 'TDF$') { + \danog\MadelineProto\Logger::log('Wrong magic', Logger::ERROR); + continue; + } + $versionBytes = stream_get_contents($fp, 4); + $version = self::unpack_signed_int($versionBytes); + \danog\MadelineProto\Logger::log("TDesktop version: $version"); + $data = stream_get_contents($fp); + $md5 = substr($data, -16); + $data = substr($data, 0, -16); + + $length = pack('l', strlen($data)); + $length = \danog\MadelineProto\Magic::$BIG_ENDIAN ? strrev($length) : $length; + + if (md5($data.$length.$versionBytes.'TDF$', true) !== $md5) { + \danog\MadelineProto\Logger::log('Wrong MD5', Logger::ERROR); + } + $res = fopen('php://memory', 'rw+b'); + fwrite($res, $data); + fseek($res, 0); + return $res; + } + throw new Exception("Could not open $fileName"); + } + public static function tdesktop_fopen_encrypted($fileName, $options = 3) { + $f = self::tdesktop_fopen($fileName, $options); + $data = self::tdesktop_read_bytearray($f); + return self::tdesktop_decrypt($data, self::$tdesktop_key); + } + public static function tdesktop_read_bytearray($fp) { + $length = self::unpack_signed_int(stream_get_contents($fp, 4)); + $data = $length ? stream_get_contents($fp, $length) : ''; + $res = fopen('php://memory', 'rw+b'); + fwrite($res, $data); + fseek($res, 0); + return $res; + } + public static function tdesktop_decrypt($data, $auth_key) { + $message_key = stream_get_contents($data, 16); + $encrypted_data = stream_get_contents($data); + + list($aes_key, $aes_iv) = self::old_aes_calculate($message_key, $auth_key, false); + $decrypted_data = self::ige_decrypt($encrypted_data, $aes_key, $aes_iv); + + if ($message_key != substr(sha1($decrypted_data, true), 0, 16)) { + throw new \danog\MadelineProto\SecurityException('msg_key mismatch'); + } + + $res = fopen('php://memory', 'rw+b'); + fwrite($res, $decrypted_data); + fseek($res, 0); + return $res; + } + public static function tdesktop($session, $new_session, $settings = []) { + set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']); + if (!isset($settings['old_session_key'])) $settings['old_session_key'] = 'data'; + if (!isset($settings['old_session_passcode'])) $settings['old_session_passcode'] = ''; + list($part_one_md5, $part_two_md5) = str_split(self::tdesktop_md5($settings['old_session_key']), 16); + + self::$tdesktop_base_path = $session.'/'; + self::$tdesktop_user_base_path = self::$tdesktop_base_path.$part_one_md5.'/'; + + $data = self::tdesktop_fopen('map'); + + $salt = self::tdesktop_read_bytearray($data); + $salt = fstat($salt)['size'] ? $salt : self::random(32); + $encryptedKey = self::tdesktop_read_bytearray($data); + + $keyIterCount = strlen($settings['old_session_passcode']) ? 4000 : 4; + + $passKey = openssl_pbkdf2($settings['old_session_passcode'], stream_get_contents($salt), 256, $keyIterCount); + self::$tdesktop_key = stream_get_contents(self::tdesktop_read_bytearray(self::tdesktop_decrypt($encryptedKey, $passKey))); + + $main = self::tdesktop_fopen_encrypted($part_one_md5, self::FILEOPTION_SAFE); + + $magic = self::pack_signed_int(0x4b); + while (($crc = stream_get_contents($main, 4)) !== $magic) { + } + $main = self::tdesktop_read_bytearray($main); + + $user_id = self::unpack_signed_int(stream_get_contents($main, 4)); + $dc_id = self::unpack_signed_int(stream_get_contents($main, 4)); + + $keys = []; + $length = self::unpack_signed_int(stream_get_contents($main, 4)); + for ($x = 0; $x < $length; $x++) { + $keys[$x]['dc_id'] = self::unpack_signed_int(stream_get_contents($main, 4)); + $keys[$x]['auth_key'] = self::tdesktop_read_bytearray($main); + } + var_dump($keys, $length, $dc_id, $user_id); + } }