├── LICENSE ├── README.md ├── qprotocal.egg-info ├── PKG-INFO ├── SOURCES.txt ├── dependency_links.txt ├── entry_points.txt ├── not-zip-safe └── top_level.txt ├── qprotocal ├── __init__.py ├── __main__.py ├── base.py ├── qq.py ├── utils │ ├── __init__.py │ ├── jce_output_stream.py │ ├── pack.py │ ├── qqtea.py │ ├── tlv.py │ └── xbin.py └── version.py ├── requirements.txt ├── setup.cfg ├── setup.py └── test ├── __init__.py ├── code.jpg └── test.py.sample /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 小白 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Android QQ 协议 Python 库 2 | 3 | ### Introduction 4 | 基于Android QQ 5.8 分析 5 | 目前未完成,想对接Telegram,在发XML格式的内容,发语音等等 6 | 7 | ### Usage 8 | 目前只有一个登录demo 9 | 10 | ``` 11 | pip install virtualenv 12 | virtualenv env --python=/bin/python3 13 | source env/bin/activate 14 | 15 | 16 | pip install -r requirements.txt 17 | 18 | cd ./test 19 | python3 ./test.py 20 | ``` 21 | -------------------------------------------------------------------------------- /qprotocal.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: qprotocal 3 | Version: 0.1.dev0 4 | Summary: Android QQ protocal for Python 5 | Home-page: http://blog.gorgiaxx.com 6 | Author: Gorgiaxx 7 | Author-email: gorgiaxx@gmail.com 8 | License: MIT 9 | Description: That's all. 10 | Keywords: qq protocal 11 | Platform: UNKNOWN 12 | -------------------------------------------------------------------------------- /qprotocal.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | setup.cfg 2 | setup.py 3 | qprotocal/__init__.py 4 | qprotocal.egg-info/PKG-INFO 5 | qprotocal.egg-info/SOURCES.txt 6 | qprotocal.egg-info/dependency_links.txt 7 | qprotocal.egg-info/entry_points.txt 8 | qprotocal.egg-info/not-zip-safe 9 | qprotocal.egg-info/top_level.txt -------------------------------------------------------------------------------- /qprotocal.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /qprotocal.egg-info/entry_points.txt: -------------------------------------------------------------------------------- 1 | 2 | # -*- Entry points: -*- 3 | -------------------------------------------------------------------------------- /qprotocal.egg-info/not-zip-safe: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /qprotocal.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | qprotocal 2 | -------------------------------------------------------------------------------- /qprotocal/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A library that provides a Python interface to the Android QQ Protocal""" 3 | from sys import version_info 4 | from .base import QQObject 5 | from .qq import QQ 6 | from .version import __version__ 7 | 8 | __author__ = 'gorgiaxx@gmail.com' 9 | 10 | __all__ = [ 11 | 'QQ' 12 | ] 13 | 14 | # QQ Number 15 | qq_number = '2537568158' 16 | # QQ Password 17 | qq_password = '123456789' -------------------------------------------------------------------------------- /qprotocal/__main__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import urllib3 4 | import certifi 5 | import future 6 | 7 | from . import __version__ as qprotocal_ver 8 | 9 | 10 | def print_ver_info(): 11 | print('python-telegram-bot {0}'.format(telegram_ver)) 12 | print('urllib3 {0}'.format(urllib3.__version__)) 13 | print('certifi {0}'.format(certifi.__version__)) 14 | print('future {0}'.format(future.__version__)) 15 | print('Python {0}'.format(sys.version.replace('\n', ' '))) 16 | 17 | 18 | def main(): 19 | print_ver_info() 20 | 21 | 22 | if __name__ == '__main__': 23 | main() -------------------------------------------------------------------------------- /qprotocal/base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from abc import ABCMeta 4 | 5 | class QQObject(object): 6 | """docstring for QQObject""" 7 | __metaclass__ = ABCMeta -------------------------------------------------------------------------------- /qprotocal/qq.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from qprotocal import (QQObject) 5 | from qprotocal.utils.xbin import Xbin 6 | from qprotocal.utils.qqtea import QQTEA 7 | from qprotocal.utils.tlv import TLV 8 | import socket 9 | import struct 10 | import time 11 | import binascii 12 | 13 | 14 | class QQ(QQObject): 15 | 16 | def __init__(self, qq_number, qq_password): 17 | self.caption = qq_number 18 | self.qq_number = int(qq_number) 19 | self.qq_number_long = struct.pack('>L', self.qq_number) 20 | self.qq_password = qq_password 21 | self.md5_pwd = Xbin.get_md5_value(self.qq_password.encode('UTF-8'), 1) 22 | self.md5_2_pwd = Xbin.get_md5_value( 23 | self.md5_pwd + bytes(4) + bytes().fromhex(hex(self.qq_number)[2:]), 1) 24 | self.ksid = bytes().fromhex('93AC689396D57E5F9496B81536AAFE91') 25 | 26 | self.imei = b'866819027236657' 27 | self.apk_ver = b'5.8.0.157158' 28 | self.share_key = bytes().fromhex('957C3AAFBF6FAF1D2C2F19A5EA04E51C') 29 | self.pub_key = bytes().fromhex('02244B79F2239755E73C73FF583D4EC5625C19BF8095446DE1') 30 | self.appid = 537042771 31 | self.pc_ver = b'\x1F\x41' 32 | self.os_type = b'android' 33 | self.os_version = b'4.4.4' 34 | self.network_type = 2 35 | self.apn = b'wifi' 36 | self.device = b'Nexus 5' 37 | self.apk_id = b'com.tencent.mobileqq' 38 | self.apk_sig = b'\xA6\xB7\x45\xBF\x24\xA2\xC2\x77\x52\x77\x16\xF6\xF3\x6E\xB6\x8D' 39 | self.time = round(time.time()) 40 | self.TGT_key = Xbin().get_random_hex(16, 1) 41 | self.rand_key = Xbin().get_random_hex(16, 1) 42 | 43 | # sso_seq 44 | self.request_id = 10000 45 | self.pc_sub_cmd = 0 46 | 47 | # sessions 48 | self.token_002c = b'' 49 | self.token_004c = b'' 50 | self.token_0058 = b'' 51 | self.session_key = bytes(16) 52 | 53 | # login state 54 | # 0 logining, 1 verify, 2 success 55 | self.login_state = 0 56 | self.last_error = '' 57 | 58 | # account info 59 | self.nick = '' 60 | self.key = b'' 61 | self.skey = b'' 62 | self.vkey = b'' 63 | self.sid = b'' 64 | self.verification = b'' 65 | self.verification_token1 = b'' 66 | self.verification_token2 = b'' 67 | self.pskey = b'' 68 | self.superkey = b'' 69 | 70 | tencent_host = socket.gethostbyname('msfwifi.3g.qq.com') 71 | tencent_port = 8080 72 | self.s = socket.socket() 73 | self.s.settimeout(3) 74 | try: 75 | self.s.connect((tencent_host, tencent_port)) 76 | except Exception as e: 77 | raise print('Time out!') 78 | 79 | def __pack(self, bin, type): 80 | if type == 0: 81 | package = b'\x00\x00\x00\x08\x02\x00\x00\x00\x04' 82 | elif type == 1: 83 | package = b'\x00\x00\x00\x08\x02\x00\x00' 84 | package += struct.pack('>H', 85 | len(self.token_002c) + 4) + self.token_002c 86 | else: 87 | package = b'\x00\x00\x00\x09\x01' 88 | package += struct.pack('>I', self.request_id) 89 | package += b'\x00\x00\x00' 90 | package += struct.pack('>H', len(self.caption) + 4) + \ 91 | self.caption.encode("ascii") + bin 92 | package = struct.pack('>I', len(package) + 4) + package 93 | return package 94 | 95 | def __unpack(self, package, flag=0): 96 | pos1 = package.find(self.caption.encode('ascii')) 97 | package = package[pos1 + len(self.caption):] 98 | if flag: 99 | package = package[pos1 + len(self.caption):] 100 | return package 101 | 102 | def __send_package(self, package): 103 | # increase_sso_seq 104 | if self.request_id > 2147483647: 105 | self.request_id = 10000 106 | self.request_id += 1 107 | 108 | # send tcp package 109 | try: 110 | # print(str(binascii.b2a_hex(package))) 111 | self.s.sendall(package) 112 | except Exception as e: 113 | raise print('Time out!') 114 | try: 115 | recv_data = self.s.recv(2048) 116 | if len(recv_data) == 0: 117 | print('Empty Package!!!!!') 118 | return self.login_state 119 | if len(recv_data) == 1440: 120 | while True: 121 | tmp_data = self.s.recv(2048) 122 | recv_data += tmp_data 123 | if len(tmp_data) < 1440: 124 | break 125 | print('recived_len: ', len(recv_data)) 126 | # self.__recive_package(recv_data) 127 | # self.s.close() 128 | return recv_data 129 | except Exception as e: 130 | raise print('wrong!!!!!') 131 | 132 | def __recive_package(self, package): 133 | if len(package) == 0: 134 | raise print('empty!!!!!') 135 | bin = self.__unpack(package) 136 | bin = QQTEA().decrypt(bin, self.session_key) 137 | head_len = struct.unpack('>L', bin[:4])[0] 138 | # split data 139 | body_bin = bin[head_len:] 140 | bin = bin[4:head_len] 141 | ssq_seq = struct.unpack('>L', bin[:4])[0] 142 | bin = bin[4:] 143 | if bin[:4] == bytes(4): 144 | bin = bin[8:] 145 | else: 146 | bin = bin[4:] 147 | foo_len = struct.unpack('>L', bin[:4])[0] 148 | bin = bin[4:] 149 | print('as4: ', foo_len) 150 | bin = bin[:foo_len - 4] 151 | print('as5: ', str(binascii.b2a_hex(bin))) 152 | 153 | foo_len = struct.unpack('>L', bin[:4])[0] 154 | service_cmd = bin[4:foo_len].decode('ascii') 155 | # Login or other operations 156 | if service_cmd == 'wtlogin.login': 157 | bin = body_bin[4:] 158 | 159 | foo_len = struct.unpack('>H', bin[1:3])[0] 160 | result = struct.unpack('>B', bin[15:16])[0] 161 | print('result: ', result) 162 | bin = bin[16:] 163 | bin = bin[:foo_len - 17] 164 | bin = QQTEA().decrypt(bin, self.share_key) 165 | 166 | if result != 0: 167 | if result == 2: 168 | self.__unpack_verification_img(bin) 169 | self.last_error = "需要输入验证码!" 170 | self.login_state = 1 171 | bin = b'' 172 | else: 173 | self.__unpack_error_msg(bin) 174 | self.login_state = 0 175 | bin = b'' 176 | 177 | # print('fuckbin1111: ', str(binascii.b2a_hex(bin))) 178 | if len(bin) == 0: 179 | return False 180 | bin = bin[7:] 181 | 182 | bin_len = struct.unpack('>H', bin[:2])[0] 183 | bin = bin[2:] 184 | # print('fucklen: ', bin_len) 185 | bin = bin[:bin_len] 186 | bin = QQTEA().decrypt(bin, self.TGT_key) 187 | TLV().tlv_unpack(self, bin) 188 | self.key = self.session_key 189 | self.login_state = 2 190 | return True 191 | else: 192 | print('service_cmd: ', service_cmd) 193 | self.__msg_handle(ssq_seq, service_cmd, body_bin) 194 | 195 | def __unpack_verification_img(self, bin): 196 | TLV().tlv_unpack(self, bin[3:]) 197 | 198 | def __msg_handle(self, ssq_seq, service_cmd, body_bin): 199 | foo_len = struct.unpack('>L', body_bin[:4])[0] 200 | body_bin = body_bin[4:] 201 | foo_bin = struct.unpack('>L', body_bin[:foo_len])[0] 202 | if service_cmd == 'OidbSvc.0x7a2_0': 203 | pass 204 | elif service_cmd == 'friendlist.getFriendGroupList': 205 | pass 206 | elif service_cmd == 'EncounterSvc.ReqGetEncounter': 207 | pass 208 | elif service_cmd == 'friendlist.getUserAddFriendSetting': 209 | pass 210 | elif service_cmd == 'SummaryCard.ReqCondSearch': 211 | pass 212 | elif service_cmd == 'friendlist.GetAutoInfoReq': 213 | pass 214 | elif service_cmd == 'SQQzoneSvc.getMainPage': 215 | pass 216 | elif service_cmd == 'friendlist.addFriend': 217 | pass 218 | elif service_cmd == 'ProfileService.GroupMngReq': 219 | pass 220 | elif service_cmd == 'OnlinePush.PbPushGroupMsg': 221 | pass 222 | elif service_cmd == 'MessageSvc.PushReaded': 223 | pass 224 | elif service_cmd == 'MessageSvc.PushNotify': 225 | pass 226 | elif service_cmd == 'StatSvc.get': 227 | pass 228 | elif service_cmd == 'SummaryCard.ReqSummaryCard': 229 | pass 230 | elif service_cmd == 'ConfigPushSvc.PushReq': 231 | pass 232 | elif service_cmd == 'OidbSvc.0x4ff_9': 233 | pass 234 | elif service_cmd == 'QQServiceDiscussSvc.ReqGetDiscuss': 235 | pass 236 | elif service_cmd == 'account.RequestReBindMobile': 237 | pass 238 | elif service_cmd == 'Signature.auth': 239 | pass 240 | elif service_cmd == 'SQQzoneSvc.publishmess': 241 | pass 242 | elif service_cmd == 'VisitorSvc.ReqFavorite': 243 | pass 244 | elif service_cmd == 'friendlist.GetSimpleOnlineFriendInfoReq': 245 | pass 246 | elif service_cmd == 'FriendList.GetTroopListReqV2': 247 | pass 248 | elif service_cmd == 'friendlist.getTroopMemberList': 249 | pass 250 | elif service_cmd == 'QQServiceDiscussSvc.ReqCreateDiscuss': 251 | pass 252 | elif service_cmd == 'QQServiceDiscussSvc.ReqAddDiscussMember': 253 | pass 254 | elif service_cmd == 'SQQzoneSvc.getApplist': 255 | pass 256 | elif service_cmd == 'friendlist.GetSimpleOnlineFriendInfoReq': 257 | pass 258 | elif service_cmd == 'friendlist.GetSimpleOnlineFriendInfoReq': 259 | pass 260 | 261 | def __pack_login_sso_msg(self, service_cmd, wup_buffer, token, is_login): 262 | msg_cookies = b'\xB6\xCC\x78\xFC' 263 | package = struct.pack('>IIIIIII', self.request_id, self.appid, 264 | self.appid, 0x01000000, 0, 0, len(token) + 4) 265 | 266 | package += token 267 | package += struct.pack('>I', len(service_cmd) + 4) + service_cmd 268 | package += struct.pack('>I', len(msg_cookies) + 4) + msg_cookies 269 | package += struct.pack('>I', len(self.imei) + 4) + self.imei 270 | # package += struct.pack('>I', len(self.ksid) + 4) + self.ksid 271 | package += struct.pack('>I', 4) 272 | package += struct.pack('>H', len(self.apk_ver) + 2) + self.apk_ver 273 | package = struct.pack('>I', len(package) + 4) + package 274 | 275 | package += struct.pack('>I', len(wup_buffer) + 4) + wup_buffer 276 | # 4 277 | package = self.__pack(QQTEA().encrypt( 278 | package, self.session_key), is_login ^ 0) 279 | return package 280 | 281 | def __unpack_error_msg(self, bin): 282 | bin = bin[9:] 283 | err_type = bin[:4] 284 | title_len = struct.unpack('>H', bin[4:6])[0] 285 | bin = bin[6:] 286 | title = bin[:title_len].decode('UTF-8') 287 | bin = bin[title_len:] 288 | message_len = struct.unpack('>H', bin[:2])[0] 289 | bin = bin[2:] 290 | message = bin[:message_len].decode('UTF-8') 291 | self.last_error = "{0}:{1}".format(title, message) 292 | 293 | def __increase_pc_sub_cmd(self): 294 | if self.pc_sub_cmd > 2147483647: 295 | self.pc_sub_cmd = 10000 296 | self.pc_sub_cmd += 1 297 | 298 | def __pack_package(self, tlv_package): 299 | tlv_data = self.pc_ver + struct.pack(">HHI", 0x0810, self.pc_sub_cmd, self.qq_number) + \ 300 | b'\x03\x07\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00' 301 | pub_key_len = len(self.pub_key) 302 | if pub_key_len > 0: 303 | tlv_data += b'\x01\x01' 304 | else: 305 | tlv_data += b'\x01\x02' 306 | tlv_data += self.rand_key + b'\x01\x02' + \ 307 | struct.pack(">H", pub_key_len) 308 | if pub_key_len > 0: 309 | tlv_data += self.pub_key 310 | else: 311 | tlv_data += struct.pack(">H", 0) 312 | 313 | # obj = self 314 | bin = QQTEA().encrypt(tlv_package, self.share_key) 315 | 316 | tlv_data += bin + b'\x03' 317 | tlv_pack = b'\x02' + struct.pack(">H", len(tlv_data) + 3) + tlv_data 318 | 319 | return tlv_pack 320 | 321 | def __pack_tlv_x2(self, code): 322 | tlv_data = TLV().tlv_x2(code, self.verification_token1) 323 | tlv_data += TLV().tlv_x8() 324 | tlv_data += TLV().tlv_x104(self.verification_token2) 325 | tlv_data += TLV().tlv_x116() 326 | 327 | cmd = struct.pack(">H", 2) 328 | tlv_num = struct.pack(">H", 4) 329 | return cmd + tlv_num + tlv_data 330 | 331 | def __pack_tlv_x9(self): 332 | tlv_data = TLV().tlv_x18(self.qq_number) 333 | 334 | tlv_data += TLV().tlv_x1(self.qq_number, self.time) 335 | tlv_data += TLV().tlv_x106(self.qq_number, self.md5_pwd, self.md5_2_pwd, 336 | self.TGT_key, self.imei, self.time, self.appid) 337 | 338 | tlv_data += TLV().tlv_x116() 339 | tlv_data += TLV().tlv_x100(self.appid) 340 | tlv_data += TLV().tlv_x108(self.ksid) 341 | tlv_data += TLV().tlv_x107() 342 | 343 | # tlv_x144 344 | tlv_x109 = TLV().tlv_x109(self.imei) 345 | tlv_x124 = TLV().tlv_x124( 346 | self.os_type, self.os_version, self.network_type, self.apn) 347 | 348 | tlv_x128 = TLV().tlv_x128(self.device, self.imei) 349 | tlv_x16e = TLV().tlv_x16e(self.device) 350 | 351 | tlv_data += TLV().tlv_x144(self.TGT_key, 352 | tlv_x109, tlv_x124, tlv_x128, tlv_x16e) 353 | tlv_data += TLV().tlv_x142(self.apk_id) 354 | tlv_data += TLV().tlv_x145(self.imei) 355 | tlv_data += TLV().tlv_x154(self.request_id) 356 | tlv_data += TLV().tlv_x141(self.network_type, self.apn) 357 | tlv_data += TLV().tlv_x8() 358 | tlv_data += TLV().tlv_x16b() 359 | tlv_data += TLV().tlv_x147(self.apk_ver, self.apk_sig) 360 | tlv_data += TLV().tlv_x177() 361 | tlv_data += TLV().tlv_x187() 362 | tlv_data += TLV().tlv_x188() 363 | tlv_data += TLV().tlv_x191() 364 | 365 | cmd = struct.pack(">H", 9) 366 | tlv_num = struct.pack(">H", 19) 367 | return cmd + tlv_num + tlv_data 368 | 369 | def login(self): 370 | tlv_package = self.__pack_tlv_x9() 371 | wup_buffer = self.__pack_package(tlv_package) 372 | 373 | # first login 374 | self.__increase_pc_sub_cmd() 375 | recv_data = self.__send_package(self.__pack_login_sso_msg( 376 | b'wtlogin.login', wup_buffer, b'', 1)) 377 | self.__recive_package(recv_data) 378 | return self.login_state 379 | 380 | def send_code(self, code): 381 | tlv_package = self.__pack_tlv_x2(code) 382 | wup_buffer = self.__pack_package(tlv_package) 383 | recv_data = self.__send_package(self.__pack_login_sso_msg( 384 | b'wtlogin.login', wup_buffer, b'', 1)) 385 | self.__recive_package(recv_data) 386 | return self.login_state 387 | 388 | def heart_beats(self): 389 | pass 390 | -------------------------------------------------------------------------------- /qprotocal/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gorgiaxx/qprotocal/570b65a3ff500ee20cd5dede5f716ffc758b37ed/qprotocal/utils/__init__.py -------------------------------------------------------------------------------- /qprotocal/utils/jce_output_stream.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | -------------------------------------------------------------------------------- /qprotocal/utils/pack.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | class Pack(object): 4 | """docstring for Pack""" 5 | def __init__(self, arg): 6 | super(Pack, self).__init__() 7 | self.arg = arg 8 | -------------------------------------------------------------------------------- /qprotocal/utils/qqtea.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import struct 3 | import random 4 | import binascii 5 | 6 | 7 | class QQTEA(object): 8 | 9 | def __init__(self): 10 | # key schedule constant 11 | self.delta = 0x9E3779B9 12 | 13 | self.round = 16 14 | 15 | self.op = 0xFFFFFFFF 16 | 17 | # append 7 '\0' in the end of the message. 18 | self.flag = bytes(7) 19 | 20 | # xor per 8 bytes 21 | def __xor(self, a, b): 22 | block = b'' 23 | for i in range(8): 24 | block += struct.pack("B", a[i] ^ b[i]) 25 | return block 26 | 27 | def __encipher(self, t, share_key): 28 | sum = self.delta 29 | 30 | keys = struct.unpack(">LLLL", share_key) 31 | uint32_1, uint32_2 = struct.unpack(">LL", t) 32 | 33 | for _ in range(self.round): 34 | uint32_1 += (((uint32_2 << 4) & 0xFFFFFFF0) + keys[0]) ^ ( 35 | uint32_2 + sum) ^ (((uint32_2 >> 5) & 0x07ffffff) + keys[1]) 36 | uint32_2 += (((uint32_1 << 4) & 0xFFFFFFF0) + keys[2]) ^ ( 37 | uint32_1 + sum) ^ (((uint32_1 >> 5) & 0x07ffffff) + keys[3]) 38 | sum += self.delta 39 | uint32_1 &= self.op 40 | uint32_2 &= self.op 41 | 42 | return struct.pack(">LL", uint32_1, uint32_2) 43 | 44 | def __decipher(self, t, share_key): 45 | sum = (self.delta << 4) & self.op 46 | 47 | keys = struct.unpack(">LLLL", share_key) 48 | uint32_1, uint32_2 = struct.unpack(">LL", t) 49 | 50 | for _ in range(self.round): 51 | uint32_2 -= (((uint32_1 << 4) & 0xFFFFFFF0) + keys[2]) ^ ( 52 | uint32_1 + sum) ^ (((uint32_1 >> 5) & 0x07ffffff) + keys[3]) 53 | uint32_1 -= (((uint32_2 << 4) & 0xFFFFFFF0) + keys[0]) ^ ( 54 | uint32_2 + sum) ^ (((uint32_2 >> 5) & 0x07ffffff) + keys[1]) 55 | sum -= self.delta 56 | uint32_1 &= self.op 57 | uint32_2 &= self.op 58 | 59 | return struct.pack(">LL", uint32_1, uint32_2) 60 | 61 | def encrypt(self, cleartext, share_key): 62 | 63 | cleartext_length = len(cleartext) 64 | 65 | # to count the number of fill bytes. 66 | padding_length = (8 - (cleartext_length + 2)) % 8 67 | padding_length += 2 + (8 if (padding_length < 0) else 0) 68 | 69 | # filling the random bytes 70 | padding_hex = b'' 71 | for _ in range(0, padding_length): 72 | padding_hex += struct.pack("B", random.randrange(1, 254)) 73 | 74 | # merge 75 | padded_cleartext = struct.pack( 76 | "B", (padding_length - 2) | 0xF8) + padding_hex + cleartext + self.flag 77 | 78 | b1 = b2 = bytes(8) 79 | result = b'' 80 | # xor per 8 bytes 81 | for i in range(0, len(padded_cleartext), 8): 82 | t = self.__xor(padded_cleartext[i:i + 8], b1) 83 | b1 = self.__xor(self.__encipher(t, share_key), b2) 84 | b2 = t 85 | result += b1 86 | return result 87 | 88 | def decrypt(self, ciphertext, share_key): 89 | ciphertext_len = len(ciphertext) 90 | # print('ciphertext_len', len(ciphertext)) 91 | pre_crypt = ciphertext[0:8] 92 | pre_plain = self.__decipher(pre_crypt, share_key) 93 | 94 | # print('pre_plain', str(binascii.b2a_hex(pre_plain))) 95 | pos = (pre_plain[0] & 0x07) + 2 96 | result = pre_plain 97 | for i in range(8, ciphertext_len, 8): 98 | # print('ciphertext', i, str(binascii.b2a_hex(ciphertext[i:i+8]))) 99 | a = self.__xor(self.__decipher(self.__xor( 100 | ciphertext[i:i + 8], pre_plain), share_key), pre_crypt) 101 | pre_plain = self.__xor(a, pre_crypt) 102 | pre_crypt = ciphertext[i:i + 8] 103 | result += a 104 | # print('result2', str(binascii.b2a_hex(result))) 105 | # print('result3', result) 106 | if result[-7:] == b'\0' * 7: 107 | # print('decrypt result: ', str(binascii.b2a_hex(result[pos+1:-7]))) 108 | return result[pos + 1:-7] 109 | else: 110 | return result 111 | -------------------------------------------------------------------------------- /qprotocal/utils/tlv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import struct 3 | import binascii 4 | import random 5 | 6 | from qprotocal.utils.qqtea import QQTEA 7 | 8 | 9 | class TLV(object): 10 | """docstring for TLV""" 11 | 12 | def __init__(self): 13 | pass 14 | 15 | # to pack tlv. 16 | def tlv_pack(self, cmd, bin, len_padding=0): 17 | return struct.pack(">HH", cmd, len(bin) + len_padding) + bin 18 | 19 | # unpack tlv package 20 | def tlv_unpack(self, qqobj, bin): 21 | tlv_count = struct.unpack('>H', bin[:2])[0] 22 | bin = bin[2:] 23 | for i in range(tlv_count): 24 | tlv_cmd = bin[:2] 25 | bin = bin[2:] 26 | tlv_len = struct.unpack('>H', bin[:2])[0] 27 | bin = bin[2:] 28 | tlv_bin = bin[:tlv_len] 29 | # print('get tlv_cmd', str(binascii.b2a_hex(tlv_cmd))[2:-1]) 30 | bin = bin[tlv_len:] 31 | 32 | # do something by tlv_cmd 33 | if tlv_cmd == b'\x01\x0A': 34 | qqobj.token_004c = tlv_bin 35 | elif tlv_cmd == b'\x01\x6A': 36 | pass 37 | elif tlv_cmd == b'\x01\x06': 38 | pass 39 | elif tlv_cmd == b'\x01\x0C': 40 | pass 41 | elif tlv_cmd == b'\x01\x0D': 42 | pass 43 | elif tlv_cmd == b'\x01\x1F': 44 | pass 45 | elif tlv_cmd == b'\x01\x20': 46 | pass 47 | elif tlv_cmd == b'\x01\x63': 48 | pass 49 | elif tlv_cmd == b'\x01\x65': 50 | pass 51 | elif tlv_cmd == b'\x01\x18': 52 | pass 53 | elif tlv_cmd == b'\x01\x08': 54 | pass 55 | elif tlv_cmd == b'\x01\x14': 56 | qqobj.token_0058 = self.tlv_x114_get0058(tlv_bin) 57 | elif tlv_cmd == b'\x01\x0E': 58 | qqobj.mST1_key = tlv_bin 59 | elif tlv_cmd == b'\x01\x03': 60 | qqobj.stweb = tlv_bin 61 | elif tlv_cmd == b'\x01\x38': 62 | xx_len = tlv_bin[:4] 63 | pass 64 | elif tlv_cmd == b'\x01\x1A': 65 | face = struct.unpack('>H', tlv_bin[:2])[0] 66 | age = struct.unpack('>B', tlv_bin[2:3])[0] 67 | gender = struct.unpack('>B', tlv_bin[3:4])[0] 68 | xx_len = struct.unpack('>B', tlv_bin[4:5])[0] 69 | tlv_bin = tlv_bin[5:] 70 | qqobj.nick = tlv_bin[:xx_len].decode('UTF-8') 71 | print('face:{0},age:{1},gender:{2},nickname:{3}'.format( 72 | face, age, gender, qqobj.nick)) 73 | elif tlv_cmd == b'\x01\x20': 74 | qqobj.skey = tlv_bin 75 | elif tlv_cmd == b'\x01\x36': 76 | qqobj.vkey = tlv_bin 77 | elif tlv_cmd == b'\x03\x05': 78 | qqobj.session_key = tlv_bin 79 | elif tlv_cmd == b'\x01\x43': 80 | qqobj.token_002c = tlv_bin 81 | elif tlv_cmd == b'\x01\x64': 82 | qqobj.sid = tlv_bin 83 | elif tlv_cmd == b'\x01\x30': 84 | time = struct.unpack('>L', tlv_bin[2:6])[0] 85 | ip = tlv_bin[6:10] 86 | print("time:{0},ip:{1}.{2}.{3}.{4}".format( 87 | time, ip[3], ip[2], ip[1], ip[0])) 88 | elif tlv_cmd == b'\x01\x04': 89 | qqobj.verification_token2 = tlv_bin 90 | elif tlv_cmd == b'\x01\x05': 91 | xx_len = struct.unpack('>H', tlv_bin[:2])[0] 92 | tlv_bin = tlv_bin[2:] 93 | qqobj.verification_token1 = tlv_bin[:xx_len] 94 | tlv_bin = tlv_bin[xx_len:] 95 | xx_len = struct.unpack('>H', tlv_bin[:2])[0] 96 | tlv_bin = tlv_bin[2:] 97 | qqobj.verification = tlv_bin[:xx_len] 98 | elif tlv_cmd == b'\x01\x6C': 99 | qqobj.pskey = tlv_bin 100 | elif tlv_cmd == b'\x01\x6D': 101 | qqobj.superkey = tlv_bin 102 | else: 103 | pass 104 | # print('unknown tlv_cmd', str(binascii.b2a_hex(tlv_cmd)), str(binascii.b2a_hex(tlv_bin))) 105 | 106 | def tlv_x1(self, qq_number, time): 107 | ip_ver = 1 108 | random32 = random.randrange(4294967295) 109 | ip_addr = 0 110 | tlv_data = struct.pack(">HIIIIH", ip_ver, random32, 111 | qq_number, time, ip_addr, 0) 112 | return self.tlv_pack(0x01, tlv_data) 113 | 114 | def tlv_x2(self, code, verification_token1): 115 | tlv_data = struct.pack(">I", len(code)) 116 | tlv_data += code.encode('ascii') 117 | tlv_data += struct.pack(">H", len(verification_token1)) 118 | tlv_data += verification_token1 119 | return self.tlv_pack(0x02, tlv_data) 120 | 121 | def tlv_x8(self): 122 | local_id = 0x0804 123 | tlv_data = struct.pack(">HIH", 0, local_id, 0) 124 | return self.tlv_pack(0x08, tlv_data) 125 | 126 | def tlv_x18(self, qq_number): 127 | ping_ver = 1 128 | sso_ver = 1536 129 | appid = 0x10 130 | app_client_ver = 0 131 | tlv_data = struct.pack(">HIIIIHH", ping_ver, 132 | sso_ver, appid, app_client_ver, qq_number, 0, 0) 133 | return self.tlv_pack(0x18, tlv_data) 134 | 135 | def tlv_x100(self, sub_appid): 136 | db_buf_ver = 1 137 | sso_ver = 5 138 | appid = 0x10 139 | app_client_version = 0 140 | main_sigmap = 0x0E10E0 141 | tlv_data = struct.pack(">HIIIII", db_buf_ver, sso_ver, 142 | appid, sub_appid, app_client_version, main_sigmap) 143 | return self.tlv_pack(0x0100, tlv_data) 144 | 145 | def tlv_x104(self, verification_token2): 146 | tlv_data = verification_token2 147 | return self.tlv_pack(0x0104, tlv_data) 148 | 149 | def tlv_x106(self, qq_number, md5_pwd, md5_2_pwd, TGT_key, imei, time, appid): 150 | TGT_ver = 3 151 | random32 = random.randrange(4294967295) 152 | tlv_data = struct.pack(">HIIIIIIII?", TGT_ver, 153 | random32, 5, 16, 0, 0, qq_number, time, 0, 1) 154 | tlv_data += md5_pwd + TGT_key 155 | 156 | tlv_data += struct.pack("I?", 0, 1) 157 | tlv_data += imei + struct.pack("I", appid) 158 | tlv_data += struct.pack("IH", 1, 0) 159 | tlv_data = QQTEA().encrypt(tlv_data, md5_2_pwd) 160 | return self.tlv_pack(0x0106, tlv_data) 161 | 162 | def tlv_x107(self): 163 | pic_type = 0 164 | tlv_data = struct.pack(">H?H?", pic_type, 0, 0, 1) 165 | return self.tlv_pack(0x0107, tlv_data) 166 | 167 | def tlv_x108(self, ksid): 168 | # tlv_data = ksid 169 | tlv_data = b'' 170 | return self.tlv_pack(0x0108, tlv_data) 171 | 172 | def tlv_x109(self, imei): 173 | tlv_data = imei 174 | return self.tlv_pack(0x0109, tlv_data) 175 | 176 | def tlv_x114_get0058(self, bin): 177 | xx_len = struct.unpack('>H', bin[6:8])[0] 178 | bin = bin[8:xx_len + 8] 179 | return bin 180 | 181 | def tlv_x116(self): 182 | m_misc_bit_map = 0x7F7C 183 | m_sub_sig_map = 0x010400 184 | sub_appid_list_length = 0 185 | tlv_data = struct.pack(">?II?", 0, m_misc_bit_map, 186 | m_sub_sig_map, sub_appid_list_length) 187 | return self.tlv_pack(0x0116, tlv_data) 188 | 189 | def tlv_x124(self, os_type, os_version, network_type, apn): 190 | os_type_len = struct.pack(">H", len(os_type)) 191 | os_version_len = struct.pack(">H", len(os_version)) 192 | network_type = struct.pack(">H", network_type) 193 | sim_operator_name = bytes(2) 194 | apn_len = struct.pack(">H", len(apn)) 195 | tlv_data = os_type_len + os_type + os_version_len + \ 196 | os_version + network_type + \ 197 | sim_operator_name + bytes(2) + apn_len + apn 198 | return self.tlv_pack(0x0124, tlv_data) 199 | 200 | def tlv_x128(self, device, imei): 201 | new_install = 0 202 | read_guid = 1 203 | guid_chg = 0 204 | dev_report = 0x01000000 205 | device_len = struct.pack(">H", len(device)) 206 | imei_len = struct.pack(">H", len(imei)) 207 | tlv_data = struct.pack(">H???I", 0, new_install, 208 | read_guid, guid_chg, dev_report) 209 | tlv_data += device_len + device + imei_len + imei + bytes(2) 210 | return self.tlv_pack(0x0128, tlv_data) 211 | 212 | def tlv_x141(self, network_type, apn): 213 | ver = 1 214 | sim_operator_name = 0 215 | apn_len = len(apn) 216 | tlv_data = struct.pack( 217 | ">HHHH", ver, sim_operator_name, network_type, apn_len) + apn 218 | return self.tlv_pack(0x0141, tlv_data) 219 | 220 | def tlv_x142(self, apk_id): 221 | tlv_num = struct.pack(">I", len(apk_id)) 222 | tlv_data = tlv_num + apk_id 223 | return self.tlv_pack(0x0142, tlv_data) 224 | 225 | def tlv_x144(self, TGT_key, tlv109, tlv124, tlv128, tlv16e): 226 | tlv_num = struct.pack(">H", 4) 227 | tlv_data = QQTEA().encrypt((tlv_num + tlv109 + tlv124 + tlv128 + tlv16e), TGT_key) 228 | return self.tlv_pack(0x0144, tlv_data) 229 | 230 | def tlv_x145(self, imei): 231 | tlv_data = imei 232 | return self.tlv_pack(0x0145, tlv_data) 233 | 234 | def tlv_x147(self, apk_ver, apk_sig): 235 | appid = 0x10 236 | apk_v_len = len(apk_ver) 237 | apk_sig_len = len(apk_sig) 238 | tlv_data = struct.pack(">IH", appid, apk_v_len) 239 | tlv_data += apk_ver 240 | tlv_data += struct.pack(">H", apk_sig_len) 241 | tlv_data += apk_sig 242 | return self.tlv_pack(0x0109, tlv_data) 243 | 244 | def tlv_x154(self, sso_seq): 245 | tlv_data = struct.pack(">I", sso_seq) 246 | return self.tlv_pack(0x0154, tlv_data) 247 | 248 | def tlv_x16b(self): 249 | ver = 1 250 | url = "game.qq.com".encode('ascii') 251 | url_len = len(url) 252 | tlv_data = struct.pack(">HH", ver, url_len) + url 253 | return self.tlv_pack(0x016b, tlv_data) 254 | 255 | def tlv_x16e(self, device): 256 | tlv_data = device 257 | return self.tlv_pack(0x016e, tlv_data) 258 | 259 | def tlv_x177(self): 260 | qq_ver = "5.2.3.0".encode('ascii') 261 | qq_ver_len = struct.pack(">H", len(qq_ver)) 262 | tlv_data = b"\x01" + b"\x53\xFB\x17\x9B" + qq_ver_len + qq_ver 263 | return self.tlv_pack(0x0177, tlv_data) 264 | 265 | def tlv_x187(self): 266 | tlv_data = b'\xF8\xFF\x12\x23\x6E\x0D\xAF\x24\x97\xCE\x7E\xD6\xA0\x7B\xDD\x68' 267 | return self.tlv_pack(0x0187, tlv_data) 268 | 269 | def tlv_x188(self): 270 | tlv_data = b'\x4D\xBF\x65\x33\xD9\x08\xC2\x73\x63\x6D\xE5\xCD\xAE\x83\xC0\x43' 271 | return self.tlv_pack(0x0188, tlv_data) 272 | 273 | def tlv_x191(self): 274 | tlv_data = bytes(1) 275 | return self.tlv_pack(0x0191, tlv_data) 276 | -------------------------------------------------------------------------------- /qprotocal/utils/xbin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import binascii 4 | import hashlib 5 | import random 6 | 7 | 8 | class Xbin(object): 9 | # def __init__(self): 10 | 11 | # get random hex by length 12 | def get_random_hex(self, length=1, is_bytes=0): 13 | random_hex = '' 14 | for _ in range(0, length): 15 | random_hex += "{:0>2x}".format(random.randrange(0, 255)) 16 | if is_bytes: 17 | return bytes().fromhex(random_hex) 18 | else: 19 | return random_hex 20 | 21 | 22 | def get_md5_value(src, is_bytes=0): 23 | md5 = hashlib.md5() 24 | md5.update(src) 25 | md5_digest = md5.hexdigest() 26 | if is_bytes: 27 | return bytes().fromhex(md5_digest) 28 | else: 29 | return md5_digest 30 | -------------------------------------------------------------------------------- /qprotocal/version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | __version__ = '5.3.0' -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gorgiaxx/qprotocal/570b65a3ff500ee20cd5dede5f716ffc758b37ed/requirements.txt -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | tag_build = dev 3 | tag_svn_revision = true 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import sys, os 3 | 4 | version = '0.1' 5 | 6 | setup(name='qprotocal', 7 | version=version, 8 | description="Android QQ protocal for Python", 9 | long_description="""\ 10 | A library that provides a Python interface to the Android QQ Protocal 11 | That's all.""", 12 | classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers 13 | keywords='qq protocal', 14 | author='Gorgiaxx', 15 | author_email='gorgiaxx@gmail.com', 16 | url='http://blog.gorgiaxx.com', 17 | license='MIT', 18 | packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), 19 | include_package_data=True, 20 | zip_safe=False, 21 | install_requires=[ 22 | # -*- Extra requirements: -*- 23 | ], 24 | entry_points=""" 25 | # -*- Entry points: -*- 26 | """, 27 | ) 28 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gorgiaxx/qprotocal/570b65a3ff500ee20cd5dede5f716ffc758b37ed/test/__init__.py -------------------------------------------------------------------------------- /test/code.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gorgiaxx/qprotocal/570b65a3ff500ee20cd5dede5f716ffc758b37ed/test/code.jpg -------------------------------------------------------------------------------- /test/test.py.sample: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import binascii 4 | import cmd 5 | import os 6 | import sys 7 | import importlib 8 | import threading 9 | sys.path.append("..") 10 | import qprotocal 11 | 12 | verification_image_file = os.getcwd() + '/code.jpg' 13 | q = qprotocal.QQ("qqnumber", "password") 14 | 15 | 16 | 17 | def qqbot_process(): 18 | # 心跳包 19 | pass 20 | 21 | class QQBotCLI(cmd.Cmd): 22 | intro = 'Type "?" or "help" for more information.\n' 23 | prompt = '>>> ' 24 | file = None 25 | 26 | def __init__(self): 27 | cmd.Cmd.__init__(self) 28 | self.qqbot=threading.Thread(target=qqbot_process) 29 | 30 | def do_exit(self, arg): 31 | 'Exit' 32 | print("\n>>>Exit!") 33 | return True 34 | raise KeyboardInterrupt 35 | 36 | # login state 37 | # 0 logining, 1 verify, 2 success 38 | def do_login(self, arg): 39 | state = q.login() 40 | if state == 2: 41 | print("Login Success!") 42 | print("token_002c: ", str(binascii.b2a_hex(q.token_002c))) 43 | print("token_004c: ", str(binascii.b2a_hex(q.token_004c))) 44 | print("session_key: ", str(binascii.b2a_hex(q.session_key))) 45 | elif state == 1: 46 | print("Verification Code:", verification_image_file) 47 | try: 48 | with open(verification_image_file, 'wb') as f: 49 | f.write(q.verification) 50 | except Exception as e: 51 | print(e) 52 | code = input("Please input Verification Code: ") 53 | state = q.send_code(code) 54 | print("token_002c: ", str(binascii.b2a_hex(q.token_002c))) 55 | print("token_004c: ", str(binascii.b2a_hex(q.token_004c))) 56 | print("session_key: ", str(binascii.b2a_hex(q.session_key))) 57 | # self.do_login(arg) 58 | else: 59 | print("Login Failed!") 60 | print(q.last_error) 61 | 62 | def cmdloop(self): 63 | cmd.Cmd.cmdloop(self) 64 | 65 | if __name__ == '__main__': 66 | cli=QQBotCLI() 67 | try: 68 | cli.cmdloop() 69 | except KeyboardInterrupt: 70 | print("\n>>>Exit!") 71 | exit(0) 72 | --------------------------------------------------------------------------------