其实,抖音逆向完成已经很久了,看到还有很多人私信我说登录过程什么样的,我翻了下源码,现在我自己都有点迷糊了。
还好,自己写的文档还是齐全的,流程如下:
1. 首先打开页面,拿到一个ms-token,这个应该很多接口都可以,我选取的是 https://creator.xxxxxx.com/aweme/v1/creator/data/v2/billboard_list/ 这个接口
2. 获取二维码检测的地址,简称回调注册,会返回一个 callback 的url
3. 先调用一次callback,从服务器拿到对应的cookie,为了与本次会话绑定
4. 正式的拿到二维码,返回有二维码png图片的base64 以及二维码内真是的url,这个看到时候需求,可以直接显示这个图片,如果服务器分开可以直接传输url,到用户服务器再转一个二维码出来,里面包含一个token,是本次二维码绑定对象
5. 一直循环调用 https://sso.xxxxxx.com/check_qrconnect/ 接口,传入二维码token会返回当前二维码状态(1.正常,2.已经扫码,等待用户确认,3.登录成功,5. 二维码过期(此时会包含一个新的二维码数据和新的token,官方页面是没有做刷新的,自己可以用这个二维码直接刷新页面使用))
6. 如果出现二次验证,上面 check_qrconnect 接口会返回弹出窗口的问题,比如需要使用短信验证码验证,还是用户自己发短信到指定的号码进行验证。当用户选择短信验证的时候,调用 https://creator.xxxxxx.com/passport/web/send_code/ 接口请求发送用户短信
6.1 用户得到验证码以后输入发送给 https://creator.xxxxxx.com/passport/web/validate_code/ 验证,如果成功,再在 6 操作中循环会返回 登录成功 成功状态
7. 不管是二次验证通过还是直接check_qrconnect 接口返回登录成功,都会返回一个 redirect_url 地址,需要访问这个 redirect_url 地址去拿登录以后得cookie。这里有2个坑,1个坑就是这个请求返回的是302,在使用requests时,一定不能让库自动跳转过去,否则cookie就拿不到了(里面加 allow_redirects=False 的原因)。第2个坑是,在第3步注册callback的时候给服务器传递了用户自己生成的私钥(加密的),在redirect返回的cookie里面有个bd_ticket_guard_server_data的东西,需要解码出来,拿出ts_sign,ticket数据组成新数据以后用私钥签名以后放入cookie,这一步不做会提示未登录的。
8. 拿到cookie,后面数据没什么难点了,直播前面是另一套,需要另外搞 (注意关键的请求记得用x_bugs.js 里面的 get_x_bugs 和 _signature 签名, get_x_bugs会签名url 和 body数据,如果有的话;_signature 就是只签名url部分)
其中逆向没有加密的几个简单的方法,比如短信验证码的加密方法:
# 加密
def commom_encrypt(num):
enc_str = ""
num_list = []
for ch in num:
val = ord(ch)
if val>=0 and val<=127 :
num_list.append(val)
elif val>=128 and val <= 2047:
num_list.append(192|31&val>>6)
num_list.append(128|63&val)
elif val>=2048 and val<= 55295 or val>=57344 and val<=65535:
num_list.append(224|15&val>>12)
num_list.append(128|63&val>>6)
num_list.append(128|63&val)
for i in range(0, len(num_list)):
enc_str += "%0.2x" % ((num_list[i]&0xff)^0x5)
return enc_str
可以对照这个加密方法进行解密工作:
# 解密
def commom_decrypt(enc_str):
num_list = []
str = ""
for i in range(0, len(enc_str), 2):
num_list.append(int(enc_str[i:i+2], 16)^5)
index = 0
while index < len(num_list):
ch = num_list[index]
if ch >= 0b11100000:
str += chr( ((ch&0b11111)<<12) | ((num_list[index+1]&0b111111)<<6) | (num_list[index+2]&0b111111) )
index += 3
elif ch >= 0b11000000:
str += chr( ((ch&0b11111)<<6) | (num_list[index+1]&0b111111) )
index += 2
else:
str += chr(ch)
index += 1
return str
至于其他一些签名算法,没必要折腾逆向,直接抠jsvmp补环境就可以了。抠代码就略过了(全放容易收律师函)
这里放出最关键的RSA Cookie方法:
# 二维码登录2: 注册回调
def register_callback(callback_url):
http = requests.session()
http.keep_alive = True
r = http.get(callback_url, timeout=3, headers=module.data.http_headers, verify=False)
if 'Set-Cookie' in r.headers:
set_cookie_list = r.headers['Set-Cookie'].replace(',', ';').split(';')
for cookie in set_cookie_list:
ckl = cookie.strip().split('=', 1)
if len(ckl)>1:
# 有些重复的要加进去
module.data.http_header_cookies[ckl[0]] = ckl[1]
# 调用callback之前前要准备的数据
module.data.http_header_cookies['bd_ticket_guard_client_web_domain'] = '2'
module.data.http_header_cookies['_bd_ticket_crypt_doamin'] = '2'
module.data.http_header_cookies['__security_server_data_status'] = '1'
# 本地生成共有、私有秘钥
[public_key, private_key] = bd_ticket_crypt.call("_private_key")
# 需要存储起来,登录需要用到
module.data.header_base_data['public_key'] = public_key
module.data.header_base_data['private_key'] = private_key
# 需要把私有秘钥加密以后发给服务器
public_xt_key = bd_ticket_crypt.call('xt', private_key)
module.data.header_base_data['Bd-Ticket-Guard-Ree-Public-Key'] = public_xt_key
bd_ticket_guard_client_data = '{"bd-ticket-guard-version":2,"bd-ticket-guard-iteration-version":1,"bd-ticket-guard-ree-public-key":"' + public_xt_key + '","bd-ticket-guard-web-version":1}'
to_base64 = base64.b64encode(bd_ticket_guard_client_data.encode('utf-8'))
module.data.http_header_cookies['bd_ticket_guard_client_data'] = to_base64.decode()
# 二维码登录6:获取最终的登录cookie,用作之后通讯
def get_login_cookie(redirect_url):
http = requests.session()
http.keep_alive = True
module.data.http_headers['Cookie'] = module.data.get_http_cookes(module.data.http_header_cookies)
# 这里返回是302,不能让他跳过去,不然没有cookie了
r = http.get(redirect_url, timeout=3, headers=module.data.http_headers, verify=False, allow_redirects=False)
if 'Set-Cookie' in r.headers:
set_cookie_list = r.headers['Set-Cookie'].replace(',', ';').split(';')
for cookie in set_cookie_list:
ckl = cookie.strip().split('=', 1)
if len(ckl)>1:
# 这个数据要特殊处理
if ckl[0] == 'bd_ticket_guard_server_data':
bd_ticket = base64.b64decode(ckl[1]).decode('utf-8')
bdtJson = json.loads(bd_ticket)
# bdtJson['client_cert']
ts_sign = bdtJson['ts_sign']
ticket = bdtJson['ticket']
timestamp = int(time.time())
t = 'ticket='+ ticket +'&path=/passport/token/beat/web/×tamp=' + str(timestamp)
req_sign = bd_ticket_crypt.call('req_sign', t, module.data.header_base_data['private_key'])
jdata = '{"ts_sign":"' + ts_sign + '","req_content":"ticket,path,timestamp","req_sign":"' + req_sign + '","timestamp":' + str(timestamp) + '}'
bd_ticket_guard_client_data = base64.b64encode(jdata.encode('utf-8')).decode('utf-8')
module.data.http_header_cookies['bd_ticket_guard_client_data'] = bd_ticket_guard_client_data
else:
# 覆盖参数
module.data.http_header_cookies[ckl[0]] = ckl[1]
整体流程代码框架,可以根据这个过程,自己去实现逆向:
# 获取第一个cookie
def get_base_cookie():
pass
# 二维码登录1: 获取注册接口
def get_register_addr():
pass
# 二维码登录2:获取服务器返回的msToken数据
def get_report_msToken():
pass
# 二维码登录2: 注册回调
def register_callback(callback_url):
pass
# 手机号码登录,需要检测是否滑块校验
def get_verify_code(phone_num):
pass
# 二维码登录3: 获取二维码进行登录
def get_login_qrcode():
pass
# 二维码登录4: 监控二维码扫码情况
def loop_check_qrcode(token_id):
pass
# 二维码登录5: 二维码二次校验
def send_check_code(verify_ticket):
pass
# 二维码登录6: 发送验证码到服务器校验
def send_validate_code(msm_code, verify_ticket):
pass
# 7. 登录以后获取用户信息
def get_user_info():
pass
# 显示登录二维码
def show_img():
pass
def test():
# 获取一个cookie
cookie = get_base_cookie()
module.data.header_base_data['msToken'] = cookie
module.data.http_header_cookies['msToken'] = cookie
# 只获取一次
module.data.header_base_data['biz_trace_id'] = module.data.get_trace_id()
# 第一阶段,注册,获取回调函数
callback_url = ''
while True:
register_result = get_register_addr()
if not register_result:
print('regist failed')
exit(0)
jvRegister = json.loads(register_result)
if int(jvRegister['status_code']) == 0:
if 'redirect_url' in jvRegister and len(jvRegister['redirect_url']) > 0:
callback_url = jvRegister['redirect_url']
break
# 第二阶段,访问回调接口,获取cookie
register_callback(callback_url)
# 头部新增
module.data.http_headers['Referer'] = 'https://creator.xxxxxx.com/'
# 使用二维码扫码登录
qr_json = get_login_qrcode()
if qr_json:
jvQrcode = json.loads(qr_json)
qr_login = False
qr_data = ''
if jvQrcode['error_code'] == 0 and 'data' in jvQrcode:
# 二维码数据
qrcode_base64 = jvQrcode['data']['qrcode']
image_bin = base64.b64decode(qrcode_base64)
with open('qr.png', 'wb') as fp:
fp.write(image_bin)
# 显示
imgthread = threading.Thread(target=show_img)
imgthread.start()
# 拿取服务器的mstoken
get_report_msToken()
# 监控扫码情况
qr_token = jvQrcode['data']['token']
qr_login,qr_data = loop_check_qrcode(qr_token)
if qr_login == True:
if qr_data:
jvQrData = json.loads(qr_data)
# 那登录需要的scid
fp_code = get_ck_fp()
module.data.http_header_cookies['tt_scid'] = fp_code
# 获取到调整地址,需要更新Cookie
if int(jvQrData['error_code']) == 0:
# cookie
if not 'ttcid' in module.data.http_header_cookies:
module.data.http_header_cookies['ttcid'] = x_bugs.call("get_ttid")
redirect_url = jvQrData['data']['redirect_url']
else:
# 错误啦
print('---------------------------------------------------------------')
print(qr_data)
exit(0)
# 等了完成会有个重定位地址,拿回来需要用私钥处理以后才能正常登录
get_login_cookie(redirect_url)
module.data.http_header_cookies['oc_login_type'] = 'LOGIN_PERSON'
# print(module.data.http_header_cookies)
print('---------------------------------------------------------------')
print('-----------------------登录成功,获取用户信息--------------------')
while True:
# 获取用户信息
print(get_user_info())
time.sleep(5)
测试:
不过异地登录会有提示风险