本文不构成任何技术操作指导或建议,仅供技术交流与学习参考。如发现本文内容存在可能违反法律规定的情况,请立即联系本人删除相关内容。(已对图片中出现的包名和应用名进行处理)
本来想着实现一下这个漫画app的模拟登录,结果抓个包一看:
![[Pastedimage20250712224444.png]](/post/ceb595b0/Pastedimage20250712224444.png)
data字段被加密,我先猜的Base64,但实际上没那么简单。
![[Pastedimage20250712225929.png]](/post/ceb595b0/Pastedimage20250712225929.png)
进入jadx看看LoginActivity:
手机验证登录部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| case R.id.login_button : if (!this.cbServiceSelect.isChecked()) { ToastUtils.show("请勾选并同意《隐私政策》和《服务协议》"); return; } else if (l(this.loginPhoneEdit.getText().toString().trim())) { if (this.loginCodeEdit.getText().toString().trim().length() != 0) { showDialog(); ApiParams apiParams = new ApiParams(); apiParams.with(Constants.RequestAction.getUser()); apiParams.with("mobile", this.loginPhoneEdit.getText().toString().trim()); apiParams.with(PluginConstants.KEY_ERROR_CODE, this.loginCodeEdit.getText().toString().trim()); ServiceProvider.postAsyn(this, this.q, apiParams, UserBean.class, this); return; } showToast(getResources().getString(R.string._please_input_validate_code)); return; } else { return; }
|
postAysn->post->.....->DataRequestWrapper->p->n->b->converTobase64()->Base64.encode()
这不仅是登录的调用栈,这个app的所有网络请求都是经过postAysn->p的过程。p是一个调度函数,根据功能的不同,实现不同的请求。
继续分析这个登录请求,在方法b中,有个方法j,拼成最终的data,然后经由方法b进行加密。
我在多次抓取请求的过程中,发现token的一些使用规则:
- 存储在运行时文件token_sp.xml中
- 由服务端生成,每次登陆时服务端会发送本次登录的会话token
- 如果是在本设备上首次登录,可以使用默认token:”manmanDefaultToken”
- 如果之前在本设备上退出登录,退出登录时服务端也会发送一个token,供下次登录使用
由于抓包得到的内容不可读,需要hook一些方法和函数来验证:
这是脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| Java.perform(function () {
let DataRequestWrapper = Java.use("com.itcode.reader.datarequest.neworkWrapper.DataRequestWrapper"); DataRequestWrapper.p.implementation = function (builder) { console.log("==== DataRequestWrapper.p() 被调用 ===="); let result = this.p.call(this, builder);
let body=result.body(); if (body != null) { let Buffer = Java.use("okio.Buffer"); let buffer = Buffer.$new(); body.writeTo(buffer); console.log("Request Body: " + buffer.readUtf8()); } return result; };
let JSONTools = Java.use("com.itcode.reader.datarequest.tool.JSONTools"); JSONTools["parseMapToJson"].implementation = function (map) { let result = this["parseMapToJson"](map); console.log(`JSONTools.parseMapToJson result=${result}`); return result; } DataRequestWrapper["convertToBase64"].implementation = function (iArr) { console.log(`DataRequestWrapper.convertToBase64 is called: iArr=${iArr}`); let result = this["convertToBase64"](iArr); console.log(` 结果字符串原始=${result}`); const resultJs = Array.from(result); const resultStr = resultJs.map(code => String.fromCharCode(code)).join(''); console.log(` 结果字符串处理后=${resultStr}`); return result; }; });
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Java.perform(function() { let DataRequestWrapper = Java.use("com.itcode.reader.datarequest.neworkWrapper.DataRequestWrapper"); DataRequestWrapper["n"].implementation = function (str, bArr, obj) { console.log(`DataRequestWrapper.n is called: str=${str}, bArr=${bArr}, obj=${obj}`); let result = this["n"](str, bArr, obj); console.log(`DataRequestWrapper.n result=${result}`); return result; }; let JSONTools = Java.use("com.itcode.reader.datarequest.tool.JSONTools"); JSONTools["parseMapToJson"].implementation = function (map) { let result = this["parseMapToJson"](map); console.log(`JSONTools.parseMapToJson result=${result}`); return result; }; })
|
最后,实现模拟登录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| import json import time import base64 import requests class Login(): def __init__(self,encode_pattern,mobile): self.url="https://xxxxxxxxxxxxxxxxxx" self.headers = { 'Content-Type': 'raw', 'Accept-Encoding': 'gzip', 'User-Agent': 'okhttp/3.14.9', 'Host': 'api.tmanga.com' } self.token='' self.encode_pattern=encode_pattern self.mobile=mobile def get_appversion_info(self): return "5.2.51^ONEPLUS A6010^android^android^9^oppo^373899aa3f886e93^98:09:CF:0C:D3:6D^869386043396293^202411010^^^^" def get_token(self): return "manmanDefaultToken" def get_precom(self): return 1 def fluter_and_add(self,param): param.pop("wkParams",None) param["p_recom"]=self.get_precom() param["info"]=self.get_appversion_info() param["token"]=self.get_token() return json.dumps(param,ensure_ascii=False) def log_encode(self,map,default): log_json=self.fluter_and_add(map) print(log_json) if default==True: times=int(time.time()) if times<1000000000: times=1000000000 sb=str(log_json)+'3'+str(times) else: times=int(time.time())+1 if times<1000000000: times=1000000000 sb=str(log_json)+str(times) bytes_data = sb.encode('utf-8') length = len(bytes_data) c_arr = [] for byte in bytes_data: c_arr.append(chr(byte & 255)) i_arr = [0] * length i3 = 0 i4 = 1 for i5 in range(length): i6 = i5 % 5 if i6 == 0: if i3 % 2 == 0: i4 = 1 else: i4 = -1 i3 += 1 c = c_arr[i5] if ord(c) >= 127: i_arr[i5] = ord(c) - ord('}') else: i_arr[i5] = ord(c) + ord('n') + ((i6 + 1) * i4) b_arr = bytearray((x & 0xFF) for x in i_arr) encoded_bytes = base64.b64encode(b_arr) return encoded_bytes def phone_login(self): params={ "mobile":self.mobile, "code":'', "api":"user/login" } params_code={ "mobile":self.mobile, "api":"verify-code/send" } code_data=self.log_encode(params_code,self.encode_pattern) print(code_data) response0 = requests.post(self.url, data=code_data, headers=self.headers) print(response0.text) params["code"]=input("输入验证码: \n") log_data=self.log_encode(params,self.encode_pattern) print(log_data) response = requests.post(self.url, data=log_data, headers=self.headers) print(response.text) self.token=response.json().get("data").get("token") print(self.token) if __name__=="__main__": phone_login=Login(mobile=xxxxxxxxxxx,encode_pattern=True) phone_login.phone_login()
|
成功实现登录:
![[Pastedimage20250713001057.png]](/post/ceb595b0/Pastedimage20250713001057.png)