Foreword: I have understood the usage of inspecage a little recently. I take the yuepao app as an example to record in detail the process of APP bag grabbing and simple reverse analysis, so as to make notes and learn together! In addition, warm reminder, there are many pictures in this article, it is recommended to connect WiFi to read!
catalog:
2. Installation and configuration
4, Code implementation (Python)
1. AES-CBC-PKCS5 encryption and decryption
4. Send request and parse response
5. Total code (including other interfaces and other data)
Text:
1, Preparation
1. APP needed
- VMOS Pro
- HttpCanary
- JustTrustMe
- Inspeckage
It has been arranged in blue play cloud, please download it by yourself!
Link:
https://huanxingke.lanzoui.com/b02069isj
password:
lptiyu
2. Installation and configuration
(1) Configuration of VMOS Pro
I. It is installed on the real machine;
II. In order to achieve better results, I opened a membership, using the following virtual machine:
III. after successfully loading and opening the virtual machine, you will see the following page and click file transfer:
IV. click I want to import:
5. Click Install Package, select inspect and justtrust me, and after confirmation, it will be installed automatically
Vi. installation of trail happy run: first install the trail happy run APP on the real machine, and then click I want to import > Application > find trail happy run > confirm the installation;
Then go back to the home page and click to enter Xpose:
8. Click the three bars in the upper left corner -- > module -- > select inspect and justtrust me module, and then restart the virtual machine to activate the module
IX. after restarting the virtual machine, the JustTrustMe module has been activated successfully by default, and then configure inspectage: enter inspectage. When Module enable and Server start are displayed, the initialization is successful, as shown in the following figure:
(2) Configuration of HttpCanary
I. It is installed on the real machine;
II. Open HttpCanary, click the three bars in the upper left corner -- > click settings in the lower left corner -- > Click SSL certificate settings, as shown in the figure:
III. click Install certificate, as shown in the figure, and then follow the prompts:
IV. return to the previous page and select the target application, as shown in the figure:
V. click the + sign in the upper right corner and select VOMS Pro, as shown in the figure
So far, the preparatory work is complete, ready to start to grab data.
2, Start grabbing data
1. Inspectage monitoring
(1) First of all, please log in your account number on the peddler APP, and then exit;
(2) Enter inspectage and select the monitoring trail happy running APP according to the following figure:
(3) After selection, keep the virtual machine running in the background, return to the real machine browser, and enter 127.0.0.1:8008. If you see the following interface, the configuration is preliminarily successful. Note that App is running is false and the top left corner switch is OFF
(4) Keep the real browser in the background, don't close it, and then proceed to the next step;
2. HttpCanary grab
(1) Open HttpCanary, click the small plane in the lower right corner, and the small plane turns green to represent the beginning of packet capture;
(2) Then go back directly, pay attention not to clean up the background and call out the virtual machine. At this time, HttpCanary will be in the lower right corner of the screen, as shown in the figure, and then click LAUNCH APP:
(3) At this time, Inspekage will wake up the treadmill APP, and the packet capture and monitoring work will start. As shown in the figure, you can see that HttpCanary has packet capture data:
(4) Then click the box navigation key of the real machine (gesture navigation is to press and hold the bottom of the screen to slide up), call out the real machine browser, and refresh the page just now. If App is running is true, the monitoring is successful. At this time, turn on the switch in the upper left corner to obtain the monitoring data. The successful interface is as follows:
So far, the packet capture and monitoring work has started, and the network request data after the start of the treadmill APP has been obtained, so we can start to analyze the data.
3, Data analysis
1. Httpcanary data section
(1) When you open HttpCanary in full screen, you can see that many requests have been captured. First, pull to the bottom and start from the initial request to see if there are any special requests that may meet our needs;
(2) As shown in the figure below, we can find that a request contains the Login keyword. We can subconsciously think that this may be a request related to user Login, which is an important part of APP packet capture
(3) Let's click to open this request, click request -- > click the preview in the lower right corner, as shown in the figure:
(4) We can see that there are several fields worthy of our attention: token, access_token,refresh_token,timestamp,nonce,jpush_id,sign:
I. The first three are token type fields. According to experience, such fields are generally generated by the server, so we will not consider them for the moment;
II. timestamp is the time stamp and nonce is the random string. They are all used to prevent replay attacks and can not be considered (see blogger)@ koastal My blog: https://blog.csdn.net/koastal/article/details/53456696);
To sum up, the most worthy consideration is the sign value. In fact, we should pay special attention to the sign value at the beginning, because it is a very common field of encryption algorithm, and we can also find that it is very similar to the md5 encrypted format, so we should write it down silently first;
(5) Then let's look at the response, as shown in the figure:
(6) Obviously, the data value in the response is base64 encoding, but when we decrypt it, we get garbled Code:
¶"Z¥ÆÍÇ Ð©<í/ÅtlÎÝοaó±8Êã½BV-0ÃúCÃæOÒ;Çÿ(^ò±°Î?t(:t1ÂTº ådä0ãùºÆ^Ü~.KÝÜ[õõ»9+ÕI5ùÄs©îÁ^Çw[Ï8 Ïϼ#>,ÌeÔPÅ¿Vú2Êç7ZzÇF£ÙÈÊn©
So obviously, this field has been encrypted once before base64, which should arouse our interest;
After getting the above data, we can go to the inspectage website to find the corresponding encryption method.
2. Inspection data section
(1) Back to the webpage of inspectage, we need to know that Crypto (generally signature algorithm, such as AES) and Hash (Hash algorithm, such as md5) on the webpage are the sources of our search for encryption methods;
(2) Let's take a look at the data in Crypto first. Click Crypto, as shown in the figure (warm tip: please turn OFF the switch in the upper left corner first, and then analyze the data, otherwise the dynamic change of the web page will affect our data searching process)
(3) We can see that a lot of data has been monitored. As mentioned above, sign may be encrypted by md5, that is, Hash. Therefore, instead of looking for sign in Crypto, we first look for the data value. How do we find it
I. search according to the order of requests: we know that the data value is the response of the request containing the login field, and this request belongs to the first batch of requests sent in HttpCanary, so we should also pull to the bottom of the inspection webpage to search;
II. Use the search function of the browser, as shown in the figure:
Then input the data value to match and find. Note: you only need to copy the first few characters of the data value to find, because the page is not completely displayed. If you use the whole data value to match, you will not find the result;
(4) From the method in (3), we have successfully found the encryption algorithm of data, as shown in the figure:
After zooming in:
It's even more obvious if you copy it
(To protect privacy, Use of some data*Instead of) SecretKeySpec(Wet2C8d34f62ndi3,AES) , Cipher[AES/CBC/PKCS5Padding] IV: K6iv85jBD8jgf32D (tiJapcbNx6AL0JmpPO0VL8V0bM7dEc4dvwth87E4HMrjE L1CVi0ww5qO+kPD5k8L0jvH/xooXvKxsADOP5x0KAsSOpV0McJUugnlZOQwm+P5usZe3H4uS93cW/X1uzkQK4jVSY0eNfnEG3Op7hDBAl6PjccMd1uVzzgNz88VvCM+LMxl1FDFv1b6MsoC5zdaesdGoxiP******** , {"uid":"*******","access_token":"333C877C9429E26D4FCDC404******","refresh_token":"CC304EDBA40FF2F2AEA2D28C******","refresh_expire":1623509738})
In other words, the encryption algorithm of data before base64 is AES-CBC symmetric algorithm, and the string uses PKCS5Padding format, in which the SecretKey value is Wet2C8d34f62ndi3, the offset IV is K6iv85jBD8jgf32D, and the string before encryption is:
{"uid":"*******","access_token":"333C877C9429E26D4FCDC404******","refresh_token":"CC304EDBA40FF2F2AEA2D28C******","refresh_expire":1623509738}
data value successfully decoded!
(5) In the same way, we click on the Hash section to try to decipher the sign value
(6) Here's a trick. Because the default display of the web page is incomplete, some values can't be seen. We can click some > > symbols in the web page to display them completely to avoid matching failure
(7) Not surprisingly, the sign value algorithm and the string before encryption were also found
Copy it:
Algorithm(MD5) [access_token006C31A39AED1ECAAA28C32554******jpush_id1507bfd3f7684******mobileDeviceId185818969******mobileModelBRQ-AN00 mobileOsVersion7.1.2nonce320975ostype1refresh_token9EF86DB536CEB764B430EC2BA4******school_id***student_num******timestamp1620917736token006C31A39AED1ECAAA28C325****uid******version86version_name3.3.6rDJiNB9j7vD2 : 7aeb494fc0063ec5c60cfd7ef8373929]
So it's md5 encryption! The string before encryption is:
access_token006C31A39AED1ECAAA28C32554******jpush_id1507bfd3f7684******mobileDeviceId185818969******mobileModelBRQ-AN00 mobileOsVersion7.1.2nonce320975ostype1refresh_token9EF86DB536CEB764B430EC2BA4******school_id***student_num******timestamp1620917736token006C31A39AED1ECAAA28C325****uid******version86version_name3.3.6rDJiNB9j7vD2
So far, the simple reverse analysis has been completed! Other interfaces, other data encryption algorithms can use similar methods to find and decode! Can start coding!
4, Code implementation (Python)
1. AES-CBC-PKCS5 encryption and decryption (crypto.py)
(1) Let's first look at the installation and use of related libraries Blog Park: https://www.cnblogs.com/niuu/p/10107212.html
(2) Code implementation:
from Crypto.Cipher import AES import base64 import re # Encryption and decryption class Crypto(object): def __init__(self): # pubkey value self.key = 'Wet2C8d34f62ndi3'.encode('utf-8') # Offset self.iv = b'K6iv85jBD8jgf32D' # AES-CBC symmetric encryption self.mode = AES.MODE_CBC # AES-CBC-PKCS5 format string self.bs = 16 self.PADDING = lambda s: s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs) # AES-CBC encryption def AESEncrypt(self, text): generator = AES.new(self.key, self.mode, self.iv) crypt = generator.encrypt(self.PADDING(text).encode("utf-8")) # Convert to base64 after encryption crypted_str = base64.b64encode(crypt) result = crypted_str.decode() return result # decrypt def AESDecrypt(self, text): text = base64.b64decode(text) cryptos = AES.new(self.key, self.mode, self.iv) plain_text = cryptos.decrypt(text) data = bytes.decode(plain_text) # Transfer to Chinese data = data.replace(r'\/', '/').encode().decode('unicode_escape') # print(json.dumps(data)) # format pat = re.compile(r'<html>(.*?)</html>') html = pat.findall(data) html = html[0] if html else "" html_ = html.replace('"', "'") # Turn to dictionary data = json.loads(data.replace(html, html_).strip('"').strip('\u000e').strip('\u0007').strip('\u0004')) return data
2. md5 encryption (Md5.py)
import hashlib def Md5(text): text = text.encode() m = hashlib.md5() m.update(text) return m.hexdigest()
3. Request data build (data.py)
from crypto import * import time # The data marked with * involves privacy, please obtain it by yourself data = {'version': '86', 'version_name': '3.3.6', 'mobileModel': 'BRQ-AN00', 'mobileDeviceId': '******', 'mobileOsVersion': '7.1.2', 'ostype': '1', 'student_num': '******', 'school_id': '***', 'uid': '******', 'token': '***************', 'timestamp': '', 'nonce': '******', 'access_token': '***************', 'refresh_token': '*****************', 'jpush_id': '*****************', 'sign': '*****************'} timestamp = int(time.time()) token = '*************' refresh_token = '************' sign_data = 'access_token{}jpush_id*********mobileDeviceId**************mobileModelBRQ-AN00mobileOsVersion7.1.2nonce********ostype1refresh_token{}school_id***student_num********timestamp{}token{}uid******version86version_name3.3.6rDJiNB9j7vD2'.format(token, refresh_token, timestamp, token) sign = Md5(sign_data) data['token'] = token data['access_token'] = token data['refresh_token'] = refresh_token data['timestamp'] = str(timestamp) data['sign'] = sign print(sign) print(data)
4. Send request and parse response (req.py)
import urllib.parse import requests url = "https://api2.lptiyu.com/v3/api.php/Login/RefreshToken" headers = {'Cookie': 'PHPSESSID=*******************', 'Connection': 'close', 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 'Content-Length': '299', 'User-Agent': 'Dalvik/2.1.0(Linux;U;Android7.1.2;BRQ-AN00Build/NZH54D)', 'Host': 'api2.lptiyu.com', 'Accept-Encoding': 'gzip'} data = {'version': '86', 'version_name': '3.3.6', 'mobileModel': 'BRQ-AN00', 'mobileDeviceId': '***************', 'mobileOsVersion': '7.1.2', 'ostype': '1', 'student_num': '************', 'school_id': '***', 'uid': '*******', 'token': '*************', 'timestamp': '******', 'nonce': '*********', 'access_token': '**********', 'refresh_token': '***********', 'jpush_id': '***************', 'sign': '*************'} data = urllib.parse.urlencode(data) response = requests.post(url=url, headers=headers, data=data).json() print(response)
5. Total code (including other interfaces and other data)
from Crypto.Cipher import AES import urllib.parse import requests import hashlib import random import string import base64 import time import json import re import os # Encryption and decryption class Crypto(object): def __init__(self): # pubkey value self.key = 'Wet2C8d34f62ndi3'.encode('utf-8') # Offset self.iv = b'K6iv85jBD8jgf32D' # AES-CBC symmetric encryption self.mode = AES.MODE_CBC # AES-CBC-PKCS5 format string self.bs = 16 self.PADDING = lambda s: s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs) # AES-CBC encryption def AESEncrypt(self, text): generator = AES.new(self.key, self.mode, self.iv) crypt = generator.encrypt(self.PADDING(text).encode("utf-8")) # Convert to base64 after encryption crypted_str = base64.b64encode(crypt) result = crypted_str.decode() return result # decrypt def AESDecrypt(self, text): text = base64.b64decode(text) cryptos = AES.new(self.key, self.mode, self.iv) plain_text = cryptos.decrypt(text) data = bytes.decode(plain_text) # Transfer to Chinese data = data.replace(r'\/', '/').encode().decode('unicode_escape') # print(json.dumps(data)) # format pat = re.compile(r'<html>(.*?)</html>') html = pat.findall(data) html = html[0] if html else "" html_ = html.replace('"', "'") # Turn to dictionary data = json.loads(data.replace(html, html_).strip('"').strip('\u000e').strip('\u0007').strip('\u0004')) return data # md5 encryption @staticmethod def Md5(text): text = text.encode() m = hashlib.md5() m.update(text) return m.hexdigest() # Generate sign/data value class Md5Encrypt(object): def __init__(self, **kwargs): # Raw json data self.data = { # Customizable values "jpush_id": "", "mobileDeviceId": "", "mobileModel": "", "mobileOsVersion": "7.1.2", # Client version number, not recommended to modify "version": "86", "version_name": "3.3.6", "ostype": "1", # Flag value # School id, default - 1 is unknown "school_id": "-1", # time stamp "timestamp": "", # Six digit random number string "nonce": "" } # A fixed value at the end of sign self.sign_str = 'rDJiNB9j7vD2' # User uid self.uid = kwargs['uid'] if 'uid' in kwargs else None # Student number self.student_num = kwargs['student_num'] if 'student_num' in kwargs else None # Set other values for key, value in kwargs.items(): self.data[key] = value # Login data value def loginData(self, code, phone): # Inherit data property data = self.data # Update flag value # Fixed to 1 data['type'] = '1' # cell-phone number data['phone'] = str(phone) # The verification code can only be obtained once in 30 days data['code'] = str(code) # time stamp timestamp = str(int(time.time())) # Six digit random number string nonce = str(random.randint(100000, 999999)) # Put in data data['timestamp'] = timestamp data['nonce'] = nonce # Generate the original sign value # Be sure to sort the dictionaries first sign_data = ''.join([(i + data[i]) for i in sorted(data)]) + self.sign_str # Encrypted sign value sign = Crypto().Md5(sign_data) # Put sign value into data data['sign'] = sign # Encrypt data data = Crypto().AESEncrypt(json.dumps(data)) # The original json value of the transferred data data = { 'key': data } # Convert to form data type data = urllib.parse.urlencode(data) # Returns the data value return data # Get user information def userData(self, token): # Inherit data property data = self.data # Delete jpush_id value del data['jpush_id'] # Update flag value # User id data['uid'] = self.uid # token value data['token'] = token # time stamp timestamp = str(int(time.time())) # Six digit random number string nonce = str(random.randint(100000, 999999)) # Put in data data['timestamp'] = timestamp data['nonce'] = nonce # Generate the original sign value # Be sure to sort the dictionaries first sign_data = ''.join([(i + data[i]) for i in sorted(data)]) + self.sign_str # Encrypted sign value sign = Crypto().Md5(sign_data) # Put sign value into data data['sign'] = sign # Convert to form data type data = urllib.parse.urlencode(data) # Returns the data value return data # Get cookie def ipData(self, token): # Inherit data property data = self.data # Update flag value # Fixed to 2 data['type'] = '2' # Student number data['student_num'] = self.student_num # User id data['uid'] = self.uid # token value data['token'] = token # time stamp timestamp = str(int(time.time())) # Six digit random number string nonce = str(random.randint(100000, 999999)) # Put in data data['timestamp'] = timestamp data['nonce'] = nonce # Generate the original sign value # Be sure to sort the dictionaries first sign_data = ''.join([(i + data[i]) for i in sorted(data)]) + self.sign_str # Encrypted sign value sign = Crypto().Md5(sign_data) # Put sign value into data data['sign'] = sign # Convert to form data type data = urllib.parse.urlencode(data) # Returns the data value return data # Refresh token value def refreshData(self, token, refresh_token): # Inherit data property data = self.data # Update flag value # Student number data['student_num'] = self.student_num # User id data['uid'] = self.uid # token value data['token'] = token # access_token value (same as token) data['access_token'] = token # refresh_token value data['refresh_token'] = refresh_token # time stamp timestamp = str(int(time.time())) # Six digit random number string nonce = str(random.randint(100000, 999999)) # Put in data data['timestamp'] = timestamp data['nonce'] = nonce # Generate the original sign value # Be sure to sort the dictionaries first sign_data = ''.join([(i + data[i]) for i in sorted(data)]) + self.sign_str # Encrypted sign value sign = Crypto().Md5(sign_data) # Put sign value into data data['sign'] = sign # Convert to form data type data = urllib.parse.urlencode(data) # Returns the data value return data # Get the leaderboard def rankData(self, token, page): # Inherit data property data = self.data # Delete jpush_id value if 'jpush_id' in data: del data['jpush_id'] if 'sign' in data: del data['sign'] # Update flag value # Fixed to 1 data['type'] = '1' # Fixed to 1 data['category'] = '1' # Student number data['student_num'] = self.student_num # User id data['uid'] = self.uid # token value data['token'] = token # Page number data['page'] = str(page) # time stamp timestamp = str(int(time.time())) # Six digit random number string nonce = str(random.randint(100000, 999999)) # Put in data data['timestamp'] = timestamp data['nonce'] = nonce # Generate the original sign value # Be sure to sort the dictionaries first sign_data = ''.join([(i + data[i]) for i in sorted(data)]) + self.sign_str # Encrypted sign value sign = Crypto().Md5(sign_data) # Put sign value into data data['sign'] = sign # Convert to form data type data = urllib.parse.urlencode(data) # Returns the data value return data # Send request class GetResponse(object): def __init__(self, **kwargs): # Request header self.headers = { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 'User-Agent': 'Dalvik/2.1.0(Linux;U;Android7.1.2;BRQ-AN00Build/NZH54D)', 'Host': 'api2.lptiyu.com', 'Accept-Encoding': 'gzip' } # Read user information userData = User().getUser() # Virtual client information jpush_id = userData['jpush_id'] mobileDeviceId = userData['mobileDeviceId'] mobileModel = userData['mobileModel'] # User identification information # User uid if 'uid' not in userData: self.encrypter = Md5Encrypt(jpush_id=jpush_id, mobileDeviceId=mobileDeviceId, mobileModel=mobileModel) self.login(autoLogin=False) else: self.uid = userData['uid'] self.token = userData['access_token'] self.refresh_token = userData['refresh_token'] # Student number if 'student_num' not in userData: print('Getting student number...') self.encrypter = Md5Encrypt(jpush_id=jpush_id, mobileDeviceId=mobileDeviceId, mobileModel=mobileModel, uid=self.uid) self.user() else: self.student_num = userData['student_num'] # Overload encryptor self.encrypter = Md5Encrypt(jpush_id=jpush_id, mobileDeviceId=mobileDeviceId, mobileModel=mobileModel, uid=self.uid, student_num=self.student_num) print('Initialization complete!') # Log in again def login(self, code=None, phone=None, autoLogin=True): if not autoLogin: print('Please login again!') phone = input('Please input mobile phone number: ') code = input('Please enter the verification code: ') print('Signing in......') url = 'https://api2.lptiyu.com/v3/api.php/Login/quickLoginV300' data = self.encrypter.loginData(code=code, phone=phone) response = requests.post(url=url, headers=self.headers, data=data).json() if 'data' not in response: raise Exception("Login failed!") tokenData = Crypto().AESDecrypt(response['data']) self.uid = tokenData['uid'] self.token = tokenData['access_token'] self.refresh_token = tokenData['refresh_token'] # Save token data User().saveUser(userData=tokenData) return tokenData # Get user information def user(self): url = 'https://api2.lptiyu.com/v3/api.php/User/User' data = self.encrypter.userData(token=self.token) response = requests.post(url=url, headers=self.headers, data=data).json() if 'data' not in response: raise Exception("Login failure!") user = Crypto().AESDecrypt(response['data']) self.student_num = user['student_num'] User().saveUser(userData=user) return user ''' # Get cookie def getIp(self): url = 'https://api2.lptiyu.com/v3/api.php/System/getIp' data = self.encrypter.ipData(self.token) response = requests.post(url=url, headers=self.headers, data=data) try: cookies = response.cookies cookies = requests.utils.dict_from_cookiejar(cookies) self.headers["Cookie"] = list(cookies.keys())[0] + '=' + list(cookies.values())[0] except: raise Exception("Login failure!") return cookies ''' # Refresh token value def refreshToken(self): url = 'https://api2.lptiyu.com/v3/api.php/Login/RefreshToken' data = self.encrypter.refreshData(token=self.token, refresh_token=self.refresh_token) response = requests.post(url=url, headers=self.headers, data=data).json() if 'data' not in response: raise Exception("Login failure!") tokenData = Crypto().AESDecrypt(response['data']) self.token = tokenData['access_token'] self.refresh_token = tokenData['refresh_token'] # Save token data User().saveUser(userData=tokenData) return tokenData # Get the leaderboard def getTotalRank(self, page): url = 'https://api2.lptiyu.com/v3/api.php/Run/getTotalRank' data = self.encrypter.rankData(token=self.token, page=page) response = requests.post(url=url, headers=self.headers, data=data).json() if 'data' not in response: raise Exception("Login failure!") rankData = Crypto().AESDecrypt(response['data'])['rank_list'] return rankData # Operation user file class User(object): def __init__(self): # token file path self.file = 'user.json' # Building client information def createInfo(self): s = string.ascii_letters + string.digits jpush_id = "".join(random.choice(s) for _ in range(0, 19)) mobileDeviceId = "".join(random.choice(string.digits) for _ in range(0, 15)) mobileModel = "".join(random.sample(string.digits, 3)) + '-' + "".join(random.sample(s, 4)) user = {'jpush_id': jpush_id, 'mobileDeviceId': mobileDeviceId, 'mobileModel': mobileModel} with open(self.file, 'w') as fp: json.dump(user, fp) # Get User information locally def getUser(self): if not os.path.exists(self.file): self.createInfo() with open(self.file, 'r') as fp: userData = json.load(fp) return userData # Save User data def saveUser(self, userData): with open(self.file, 'r') as fp: userData_ = json.load(fp) for key, value in userData.items(): userData_[key] = value with open(self.file, 'w') as fp: fp.write(json.dumps(userData_)) if __name__ == '__main__': handler = GetResponse() fp = open('lptiyu.csv', 'w', encoding='utf-8') fp.write('ranking,full name,Number of runs\n') for j in range(1, 4): rankData_ = handler.getTotalRank(page=j) for i in rankData_: score = i['score_num'] rank = i['rank'] name = i['name'] fp.write('%s,%s,%s\n' % (rank, name, score)) fp.close()
Well, that's all for today's summary and sharing. Thank you for reading!