八期day04-基础案例
一 抓包逆向案例
1.1 金树林.apk
1.1.1 目标
# 0 抓取https包,不需要反编译,就可以做爬虫,简单案例
# 1 豌豆荚---》搜索app--》金树林---》下载
# 2 安装到手机上:
-手机使用数据线,链接到电脑,前提是配置了adb,配置了抓取https的包
-adb install app位置和名字
# 3 手机上看到app
# 4 对 这个app的目标:
1 使用python编写代码,实现发送验证码
2 自动注册
3 自动登录
4 登录后,可以发送查询请求,查询数据
# 5 来到注册页面
# 6 打开charles,抓包,抓取获取验证码的包
-请求地址是:https://miappshop.jshulin.com/memberLogin/phoneCode?phone=18953675221&serviceType=5
-请求头:没有特殊的,可以尝试,先不带请求头试一下
-请求方式:get,没有请求体
# 7 使用pyton写代码,发送验证码
1.1.2 破解验证码
import requests
phone=input('请输入要发送验证码的手机号:')
res=requests.get('https://miappshop.jshulin.com/memberLogin/phoneCode?phone=%s&serviceType=5'%phone,verify=False)
print(res.json())
1.1.3 自动注册
# 8 抓包软件
# 9 抓到的包
-请求地址是:https://miappshop.jshulin.com/memberLogin/memberRegister
-请求方式:post
-请求体:{"phone":"18953675221","fid":"","password":"lqz12345","phoneCode":"8888"}
-请求头不用携带
# 10 写代码实现
import requests
'''
发送请求,编码格式,常用又三种
1 urlencoded: key=value&key2=value2
2 json: {"phone":"18953675221","fid":"","password":"lqz12345","phoneCode":"8888"}
3 form-data:上传文件用
'''
phone = input('请输入手机号:')
code = input('请输入验证码:')
password = input('请输入密码:')
data = {"phone": phone, "fid": "", "password": password, "phoneCode": code}
res = requests.post('https://miappshop.jshulin.com/memberLogin/memberRegister', json=data,verify=False)
print(res.text)
1.1.4 自动登录
# 11 抓包,登录包
-请求地址是:https://miappshop.jshulin.com/memberLogin/login
-请求方式是:post
-请求体:{"password":"lqz123456","username":"17717823244"}
# 12 使用python发送请求
import requests
phone = input('请输入手机号:')
password = input('请输入密码:')
data = {"password": password, "username": phone}
res = requests.post('https://miappshop.jshulin.com/memberLogin/login', json=data, verify=False)
print(res.text)
1.1.5 登录后查询红酒
# 13 抓包
-请求地址是:https://miappshop.jshulin.com/pro/searchByPage
-请求方式:post
-请求体:{"cityNo":"","limit":10,"orderByContent":"","page":1,"productCategoryId":"1649599340962672642","enabled":1}
# 14 使用代码重现
import requests
data = {"cityNo": "", "limit": 10, "orderByContent": "", "page": 1, "productCategoryId": "1649599340962672642",
"enabled": 1}
header = {
'Mobile-Token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxNjcxMTMyMDA2MDczNjA2MTQ1IiwiZXhwIjoxNjg3MjY3OTgyLCJpYXQiOjE2ODcyNjQzODIsInVzZXJJZCI6IjE2NzExMzIwMDYwNzM2MDYxNDUiLCJ1c2VybmFtZSI6IjE3NzE3ODIzMjQ0In0.xZ5Qslf4pY2ahDev4j0b3H6-CZa8pXiltvirqTme7Os',
'user-agent': 'Mozilla/5.0 (Linux; Android 11; Pixel 2 XL Build/RP1A.201005.004.A1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 uni-app Html5Plus/1.0 (Immersed/28.0)'
}
res = requests.post('https://miappshop.jshulin.com/pro/searchByPage', json=data, verify=False,headers=header)
print(res.text)
1.1.6 补充(请求参数转字典)
# 今日南川app登录
s='appId=32&hashSign=8356ebae71a0aa643f87ad4c5691a456&imgUrl=&lat=29.568295&lng=106.559123&loginName=18953675221&nickName=&openId=&place=%E9%87%8D%E5%BA%86&pwd=25d55ad283aa400af464c76d713c07ad&sessionId=392032c5-09c8-4c3c-bb17-16a1dc49f7fc&token=&type='
def query_to_dict(s):
return { item.split('=')[0]:item.split('=')[1]for item in s.split('&')}
print(query_to_dict(s))
1.1.7 补充 url编码解码
from urllib import parse
print(parse.quote('上海'))
print(parse.unquote('%E4%B8%8A%E6%B5%B7'))
1.2 爱安丘.apk
1.2.1 目标
# 爱安丘--》豌豆荚下不到----》官网下载:https://app.litenews.cn/download/UGtqYml2dWtWL1NNaXN5WElneE1PQT09#/vote?id=Mjk5OTpkMmNjZQ&active=0&type=0&orgid=137
# 破解两个版本(有变化)
-老版本不需要反编译(爱安丘-v228.apk),只抓包即可---》发送短信验证码---》只抓包
-新版需要好反编译(爱安丘-1.0.5最新版.apk)---》用户名密码登录----》反编译+抓包
# 操作步骤:
1 安装老版本
2 打开charles抓包
3 *******************************注意事项*******************************
-你们目前是抓不了这个app,无论新老版本,加入了客户端证书校验,无法抓包,需要借助于:LSposed+JustTrustMe
-LSPosed-v1.8.6-6712-zygisk-release.zip
-JustTrustMe.apk
-把LSPosed刷入到手机---》面具---》重启---》
4 可以顺利的抓包了
-请求地址:https://app-auth.iqilu.com/member/phonecode
-请求方式:post
-请求体:{"phone":"18953675221"}
-响应格式:
{
"code": 1,
"data": "SCbQf7NqSzUo40aLaIUMZihnmFx6w6HpuQU94cyAoog="
}
6 开始模拟代码
1.2.2
IMEI(International Mobile Equipment Identity)是【国际移动设备识别码】的缩写,它是一个唯一标识符,用于识别移动设备,如手机、平板电脑等。IMEI由15位数字组成,每一位都有特定的含义
# IMEI的规则如下:(15位的16进制)
前六位(TAC):型号核准号码,用于识别设备的制造商和设备类型。
接下来的两位(FAC):最终装配代码,表示设备的最终装配站。
后面的六位(SNR):串号,表示设备的序列号。
最后一位(SP):校验位,用于验证IMEI的有效性
# python模拟产生imei
def generate_imei(): # 跟useragent类似,要变换一下
# # 生成随机的TAC(前六位)
tac = ''.join(random.choices('0123456789', k=6))
# 生成随机的FAC(接下来的两位)
fac = ''.join(random.choices('0123456789', k=2))
# 生成随机的SNR(后面的六位)
snr = ''.join(random.choices('0123456789', k=6))
# 计算校验位
imei_base = tac + fac + snr
imei_list = [int(digit) for digit in imei_base]
check_digit = sum(imei_list[::-2] + [sum(divmod(d * 2, 10)) for d in imei_list[-2::-2]]) % 10
# 生成最终的IMEI
imei = imei_base + str((10 - check_digit) % 10)
return imei
# return "".join(random.choices('0123456789abcdef', k=15))
1.2.3 验证码登录(老版本v228)
import requests
from utils import generate_imei, header_str_to_dict
phone = input('请输入手机号:')
data = {"phone": phone}
header_s = '''
user-agent chuangqi.o.137.com.iqilu.app137/0.0.28.108
accept */*
orgid 137
cq-agent {"os":"android","imei":"%s","osversion":"11","network":"none","version":"0.0.28.108","core":"1.6.4"}
Content-Type application/json
Content-Length 23
Host app-auth.iqilu.com
Connection Keep-Alive
Accept-Encoding gzip
Cookie orgid=137
''' % generate_imei()
# 把请求头字符串转成字典
header = header_str_to_dict(header_s)
res = requests.post('https://app-auth.iqilu.com/member/phonecode', json=data, verify=False, headers=header)
response = res.json()
print(response)
key = response['data'] # 试用验证码登录,需要携带这个串
### 验证码登录功能
code = input('请输入手机收到的验证码:')
data = {"phone": phone,
"code": code, # 手机收到的验证码,并不是某个接口返回的数据中的code
"key": key, # 上次咱们破解发送验证码功能返回的key,如果没有是不行的
"password": "",
"captcha": "",
"captchaKey": ""
}
res = requests.post('https://app-auth.iqilu.com/member/login', headers=header, json=data,verify=False)
print(res.text)
'''
{"code":1,"data":{"orgid":137,"openid":"em-BZvxBrDdoGVgLsueGEQ","phone":"","password":"","salt":"","nickname":"网友c7f6857be8","avatar":"https://img11.iqilu.com/avatar.png","id":62180313,"isVirtual":0,"parentid":0,"groupid":0,"menu":["tv","radio","party","zhengwu","service","share","member","comment","osps","shop","signup","vote","snapshot","live","practice","invoice","healthy_two","govask_two","lottery_two"],"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJnaWQiOjAsInQiOjE2ODcyNjgzMzcsIm9wZW5pZCI6ImVtLUJadnhCckRkb0dWZ0xzdWVHRVEiLCJpc3MiOiJjcSIsIm5pY2tuYW1lIjoi572R5Y-LYzdmNjg1N2JlOCIsImF2YXRhciI6Imh0dHBzOi8vaW1nMTEuaXFpbHUuY29tL2F2YXRhci5wbmciLCJ0aW1lIjoxNjg3MjY4MzM3LCJvcmdpZCI6MTM3LCJwbGF0Zm9ybSI6ImNodWFuZ3FpIn0.M5jH2YAs2yqxQ76ALF1RUrYTrEWmXFFJb4D-GU_zGyM","memberToken":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJnaWQiOjAsInQiOjE2ODcyNjgzMzcsIm9wZW5pZCI6ImVtLUJadnhCckRkb0dWZ0xzdWVHRVEiLCJpc3MiOiJjcSIsIm5pY2tuYW1lIjoi572R5Y-LYzdmNjg1N2JlOCIsImF2YXRhciI6Imh0dHBzOi8vaW1nMTEuaXFpbHUuY29tL2F2YXRhci5wbmciLCJ0aW1lIjoxNjg3MjY4MzM3LCJvcmdpZCI6MTM3fQ.8zvzD_B6n6T6bkEnIqNChvyqHwmH-nRKDIMe9Ios8Zo","totalScore":50,"score":50,"needPassword":true,"islock":false,"isslient":false,"has_set_invite":1,"invite_code":"110QMX","invite_url":"https://app.iqilu.com/share/aS0xMzctNjIxODAzMTM.html"}}
'''
# 前后端分离项目:app,没有cookie这个东西,cookie只在浏览器上有,而app一般使用jwt的认证,经常看到token,三段式,以后只要有了token
# 就是登录状态,携带token发送请求,就是当前登录用户,跟之前web爬虫的cookie是一种东西
# 安卓开发原声开发和混合开发---》原生安卓中套网页:https://app.iqilu.com/share/aS0xMzctNjIxODAzMTM.html
1.2.4 验证码解释
# 1 咱们,使用自己手机,接收验证码,手动输入---》这个有点麻烦
# 2 专门的接码平台(比较普遍)
-花钱
-它给你手机号---》它收到验证码---》调用它某个接口---》验证码就给你了---》嵌入到程序中---》不需要认操作了
-它会提供api---》requests发送请求那即可
-使用web爬虫技术,解析网页中的验证码
# 3 卡商
1.2.5 手机号密码登录(新版本最新版)
反编译,找位置(根据地址:member/login)
hook.py
####1 手机端启动frida-serve
adb shell
su
cd /data/local/tmp/
ls
./frida-server-16.0.19-android-arm64
####2 设置端口转发
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
# 3 前台运行的包名
# 枚举手机上的所有进程 & 前台进程
import frida
# 获取设备信息
rdev = frida.get_remote_device()
# 枚举所有的进程
processes = rdev.enumerate_processes()
for process in processes:
print(process)
# 获取在前台运行的APP
front_app = rdev.get_frontmost_application()
print(front_app)
##############################
# 4 hook
import frida
import sys
# 连接手机设备
rdev = frida.get_remote_device()
session = rdev.attach("爱安丘")
scr = """
Java.perform(function () {
// 包.类
var EncryptUtil = Java.use("com.iqilu.core.util.EncryptUtil");
EncryptUtil.getMD5.implementation = function(str){
console.log("明文:",str);
var res = this.getMD5(str);
console.log("md5加密结果=",res);
return res;
}
});
"""
script = session.create_script(scr)
def on_message(message, data):
print(message, data)
script.on("message", on_message)
script.load()
sys.stdin.read()
用户名密码登录代码
import requests
from utils import generate_imei, header_str_to_dict, encrypt_data
session = requests.session()
imei = generate_imei()
header_s = '''
encrypt 1
version 1.0.5
orgid 137
User-Agent null chuangqi.o.137.com.iqilu.app137/1.0.5
platform android
imei bae6495482efee22
CQ-AGENT {"os":"android","brand":"google","imei":"%s","osversion":"11","network":"unknown","version":"1.0.5","core":"2.2.1.1"}
cq-token
Content-Type application/json; charset=UTF-8
Content-Length 93
Host app-auth.iqilu.com
Connection Keep-Alive
Accept-Encoding gzip
Cookie orgid=137
''' % imei
header = header_str_to_dict(header_s)
phone = input('请输入手机号:')
password = input('请输入密码:')
password = encrypt_data(password)
data = {"codeKey": "", "password": password, "code": "", "phone": phone, "key": ""}
res = session.post('https://app-auth.iqilu.com/member/login?e=1', headers=header, json=data,verify=False)
print(res.text)
utils.py
import random
from Crypto.Cipher import AES
import base64
def generate_imei():
# 生成随机的TAC(前六位)
tac = ''.join(random.choices('0123456789', k=6))
# 生成随机的FAC(接下来的两位)
fac = ''.join(random.choices('0123456789', k=2))
# 生成随机的SNR(后面的六位)
snr = ''.join(random.choices('0123456789', k=6))
# 计算校验位
imei_base = tac + fac + snr
imei_list = [int(digit) for digit in imei_base]
check_digit = sum(imei_list[::-2] + [sum(divmod(d * 2, 10)) for d in imei_list[-2::-2]]) % 10
# 生成最终的IMEI
imei = imei_base + str((10 - check_digit) % 10)
return imei
# return "".join(random.choices('0123456789abcdef', k=16))
def query_to_dict(s):
return {item.split('=')[0]: item.split('=')[1] for item in s.split('&')}
def header_str_to_dict(header_str):
res = [item for item in header_str.split('\n')]
res = res[1:len(res) - 1]
d = {item.split('\t')[0]: item.split('\t')[1] for item in res}
return d
# 加密的字符串,必须是固定长度,处理成固定长度
def pad_data(data):
# 计算需要填充的字节数
pad_len = AES.block_size - (len(data) % AES.block_size)
# 使用填充字节进行填充
padding = bytes([pad_len] * pad_len)
padded_data = data + padding
return padded_data
def encrypt_data(password):
# 创建 AES 密码对象
# cipher = AES.new(key, AES.MODE_CBC, iv)
# 密钥(16 字节)
key = b'6d6656a37cdb7977c10f6d83cab168e9'
# 初始化向量(16 字节)
iv = b'0000000000000000'
cipher = AES.new(key, AES.MODE_CBC, iv)
# 填充数据
padded_data = pad_data(password.encode('utf-8'))
print(padded_data)
# 加密数据
encrypted_data = cipher.encrypt(padded_data)
return base64.b64encode(encrypted_data).decode('utf-8')
if __name__ == '__main__':
print(encrypt_data('1234567'))
# qV+iHZqcr+cEWpKgOoDx8g==
# qV+iHZqcr+cEWpKgOoDx8g==
二 抓包反编译案例
2.1 X大夫
2.1.1 目录
# 模拟登录
2.1.2 操作步骤
#1 请求地址
https://api.niaodaifu.cn/v4/site/loginnew
#2 请求体
devisetoken 1507bfd3f6dd1eafc0f
password lqz12345
mobile 18953675221
channel android
sign afc1963626ccdb43d5b394017b206144
time 1687255476
mechanism 0
platform 1
#3 感觉,需要逆向的请求体中的字段devisetoken和sign,去掉devisetoken重新发包,发现正常响应,所以只需要逆向sign
#4 jadx打开X大夫
#5 搜索内容是:"sign"
#6 核心代码如下
# 获取当前秒级别的时间戳
long currentTimeMillis = System.currentTimeMillis() / 1000;
SafeUtils.getSign(currentTimeMillis)
# 6 代码如下 SafeUtils.getSign
public static String getSign(long j) {
try {
MessageDigest instance = MessageDigest.getInstance("MD5");
String substring = HexDump.toHex(instance.digest(("niaodaifu" + j).getBytes())).substring(12, 30);
String substring2 = HexDump.toHex(instance.digest((channel + j).getBytes())).substring(12, 26);
return substring + substring2;
} catch (Exception unused) {
return "";
}
}
# 7 转成python 代码为:
import hashlib
def md5(data_string):
obj = hashlib.md5()
obj.update(data_string.encode('utf-8'))
return obj.hexdigest()
j = "1687256602"
v1 = md5(f"niaodaifu{j}")[12:30]
v2 = md5(f"android{j}")[12:26]
sign = v1 + v2
print(sign)
2.2.3 实现代码
import requests
from utils import query_to_dict
import time
import hashlib
def md5(data_string):
obj = hashlib.md5()
obj.update(data_string.encode('utf-8'))
return obj.hexdigest()
def get_sign():
t = int(time.time())
v1 = md5(f"niaodaifu{t}")[12:30]
v2 = md5(f"android{t}")[12:26]
sign = v1 + v2
return sign
print(get_sign())
phone = input('请输入手机号:')
password = input('请输入密码:')
sign = get_sign()
print('----',sign)
q = 'password=%s&mobile=%s&channel=android&sign=%s&time=%s&mechanism=0&platform=1' % (
password, phone, sign,int(time.time()))
data = query_to_dict(q)
res = requests.post('https://api.niaodaifu.cn/v4/site/loginnew', json=data, verify=False)
print(res.json())
2.2 油联合伙人
2.2.1 目标
# 登录
2.2.2 密码破解操作步骤
# 1 抓包
-请求地址:https://chinayltx.com/app/api/v1/partnerLogin/login
-请求体:
phone 18953675221
password 166acc691782f077c5c7c7c10fa39b1c
-请求头:
X-App native
X-Noncestr 123456
X-OS partnerApp_android
X-Req-Time 1687258564222
X-Sign 869a52194c795e272475b4e50d4e80fa
X-Token
X-UserID
Content-Type application/x-www-form-urlencoded
Content-Length 59
Host chinayltx.com
Connection Keep-Alive
Accept-Encoding gzip
User-Agent okhttp/3.10.0
# 2 逆向
根据 请求地址搜索: partnerLogin/login
# 3 代码如下(使用了Retrofit2 模块,发送请求的,@ 是java的注解)
@FormUrlEncoded
@POST("api/v1/partnerLogin/login")
Observable<HttpResult<LoginInfo>> submitLogin(@Field("phone") String str, @Field("password") String str2);
# 4 只要 类名.submitLogin("18953675221","lqz12345"),就会向api/v1/partnerLogin/login发送请求,使用FormUrlEncoded编码格式
# 5 查找用例submitLogin
# 6 找到loginWithToken---》使用查找用例找不到,直接全局搜索
# 7 找到buildObservable
@Override // com.yltx.oil.partner.mvp.domain.UseCase
public Observable<HttpResult<LoginInfo>> buildObservable() {
return this.mRepository.loginWithToken(this.name, this.pwd);
}
# 8 查找密码设置位置
public void submitLogin(String str, String str2) {
this.mLoginUseCase.setName(str);
this.mLoginUseCase.setPwd(Md5.md5(str2));
this.mLoginUseCase.execute(new LoginSubscriber(this.view));
}
# 9 确认是md5
2.2.3 签名的破解步骤
# 1 搜索 X-Sign
# 2 当前类中找哪里使用:private static final String PARAM_SIGN = "X-Sign";
# 3 找到getRequestHeaders方法
public Headers getRequestHeaders(Headers headers) {
Headers.Builder newBuilder = headers.newBuilder();
newBuilder.add(PARAM_APP, this.appType);
newBuilder.add(PARAM_NONCESTR, this.noncestr);
newBuilder.add(PARAM_OS, this.clientType);
newBuilder.add(PARAM_REQ_TIME, this.reqTime);
newBuilder.add(PARAM_SIGN, this.sign);
newBuilder.add(PARAM_TOKEN, this.token);
newBuilder.add(PARAM_USER_ID, this.userId);
return newBuilder.build();
}
# 4 根据this.sign 找到
private String sign(String str) {
return Md5.md5(this.token + this.reqTime + this.noncestr.substring(2) + str).toLowerCase();
}
# 5 使用python编写
import hashlib
token = ""
reqTime = "1657201079926"
nonce_str = "123456"
nonce_str_sub_2 = nonce_str[2:]
body_string = "phone=18630099999&password=4297f44b13955235245b2497399d7a93"
encrypt_string = f"{token}{reqTime}{nonce_str_sub_2}{body_string}"
obj = hashlib.md5()
obj.update(encrypt_string.encode('utf-8'))
res = obj.hexdigest()
print(res)
2.2.4自动登录代码如下
import requests
import hashlib
import time
phone = input('请输入手机号')
password = input('请输入密码')
def get_md5(data_string):
obj = hashlib.md5()
obj.update(data_string.encode('utf-8'))
return obj.hexdigest()
password = get_md5(password)
data = {
'phone': phone,
'password': password
}
token = ''
req_time = str(int(time.time() * 1000))
nonce_str = "123456"
nonce_str_sub_2 = nonce_str[2:]
body_string = f"phone={phone}&password={password}"
sign = encrypt_string = f"{token}{req_time}{nonce_str_sub_2}{body_string}"
sign = get_md5(encrypt_string)
header = {
"X-App": "native",
"X-Noncestr": nonce_str,
"X-OS": "partnerApp_android",
"X-Req-Time": req_time,
"X-Sign": sign,
"X-Token": token,
"X-UserID": ""
}
res = requests.post('https://chinayltx.com/app/api/v1/partnerLogin/login', data=data, verify=False, headers=header)
print(res.text)