某海外市场运营商app登录协议逆向

文章正文
发布时间:2025-11-28 14:44

1.png (14.53 KB, 下载次数: 3)

下载附件

2025-7-22 20:31 上传


首先拿到应用的格式为xapk,这个格式其实就是把包的其他资源分开打包了。


后缀改为zip解压后得到几个文件如下图:

2.png (58.15 KB, 下载次数: 3)

下载附件

2025-7-22 20:31 上传

3.png (82.34 KB, 下载次数: 2)

下载附件

2025-7-22 20:31 上传



大致看出来有com的这个就是核心代码apk文件了,咱们主要分析这个包体,看看怎么个事


首先通过一个root过的真机进行调试,首先把xapk丢入手机中进行安装,点击运行后出现的界面顺序如下两张图


先是出现了登录界面,然后便跳出另外一个界面了。


我先打开Manifest.xml文件分析一下主Activity的调用为

4.png (8.78 KB, 下载次数: 3)

下载附件

2025-7-22 20:36 上传


然后adb进入手机的启动frda的服务短程序


使用objection调用其内存中存在的Activity,这里我直接调用SplashScreenActivity,成功跳到手机号输入界面。


启动Reqable,进行抓包,输入手机号码,点击GET OTP按钮后出现登录请求的包体数据,全部进行加密了。
NP8AqqGZMadYeIN5AcedoWFyiYW9Rln4dHDFFaNLy8umH6qSwAOppb7O/SmycgtWOL+A3ndWEYtOGbnlYpnx+9/Kr8sWtrS+wk/PFgBXmfRb6Es6LpCYdycxhoX2+tRvloAhJ8FXCo+FDC6ZO+QJwM1Hcbcc6eLzfbZ6Pp+0dID7p90nJLFClcfCUk5T8q8jCtBR3l4BxCfUAZcgvrAlp487ITaGyxhMp6aogUrm4AcwFzdPhgG2XpMrn60xWKx/vBuq7wYmJH7h6VKcrjjS4zNQaBUbaODFBJMyXeWfSw3Ctf8D9IfXOtzPr+NJmGGfD+grlwFVfm2Cbd3OqkYD9xu0udjf3gjw9Nw/S85aEcj0lN5VaepJ2hRoSCPK5589yxB+K8CEwoiNDWZJMg4xwca7We9mffeeHWC39IXyvtTqEX7WHOY+aN9zQU6cu2SjUuk8fLuYWCyrY0IdIxc/2pKFyh2pxr5gQSW2teobrgmLUNBBLgRXwN2a6Oz+SYAF+a4n/+5Rr/A=


然后输入验证码后出现真实的请求包体数据,同样也进行加密了。
SbCgfyIaaDwVpdFfDj0noFoSauFfqOkBfA6X1nb+zwj5EPwWmfvVApVZkXTjurcMHShSo+gKTwOuAq34AFzHn2SR2IZU3kJ2qbX8NVWNFeykBNDwD0lu0R/uiv4n8rR7DLg0gGIfMgtMxhZbnk9tTrc1VJqAL83kzJHRK63OadSNKM8IWE2QrOyhmyUt2F0onKwJpcY1/C5CLatJnggBZADmTRUmsKSZo/4WHlwUQaxMNF4XVb4lfKYtCjn3XWQEEFO3WCQeANg=


我收到了验证码登录后的响应并没有进行加密:
arg[1]: [response={"errorCode":-304,"success":null,"errorMsg":"OTP is incorrect or expired; try again.","httpStatusCode":200}]


OTP is incorrect or expired; try again. 这个提示就是验证码错误的响应。


继续打开开源脚本r0trace.py进行hook此应用 输入完整的验证码后,然后查看堆栈信息,发现如下重点信息:

5.png (16.02 KB, 下载次数: 3)

下载附件

2025-7-22 20:49 上传


这个方法应该就是登录后发送的请求加密函数了


打开jadx找到此函数

[Java] 纯文本查看 复制代码

public static String encryptToString(String str) { try { return getCompletePayload(str, readPublicKeyFromFile(KEY_FILE)); } catch (IOException unused) { return null; } }




[Java] 纯文本查看 复制代码

private static String getCompletePayload(String str, PublicKey publicKey) { try { String substring = UUID.randomUUID().toString().substring(0, 24); String encrypt = encrypt(substring, str); String encrypt2 = encrypt(substring, String.valueOf(System.currentTimeMillis())); Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(1, publicKey); byte[] doFinal = cipher.doFinal(substring.getBytes()); String encodeToString = android.util.Base64.encodeToString(doFinal, 0, doFinal.length, 2); return "data=" + URLEncoder.encode(encrypt, CHARSET) + "&key=" + URLEncoder.encode(encodeToString, CHARSET) + "×tamp=" + URLEncoder.encode(encrypt2, CHARSET) + "&dataContentType=" + URLEncoder.encode(ContentType.JSON_PROXY_MONEY, CHARSET); } catch (UnsupportedEncodingException | InvalidKeyException | NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException unused) { return null; } }



[Java] 纯文本查看 复制代码

String substring = UUID.randomUUID().toString().substring(0, 24);


这个是得到一个UUID的密钥


[Java] 纯文本查看 复制代码

String encrypt = encrypt(substring, str);


这个就是加密数据的方法


[Java] 纯文本查看 复制代码

Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(1, publicKey); byte[] doFinal = cipher.doFinal(substring.getBytes());


这里是拿到公钥给加密的密码进行再次加密,用的是RSA非对称加密。


继续分析encrypt函数代码

[Java] 纯文本查看 复制代码

private static String encrypt(String str, String str2) { byte[] bArr = new byte[24]; if (str == null) { new SecureRandom().nextBytes(bArr); } else { System.arraycopy(str.getBytes(), 0, bArr, 0, 24); } try { SecretKey generateSecret = SecretKeyFactory.getInstance("DESede").generateSecret(new DESedeKeySpec(bArr)); Cipher cipher = Cipher.getInstance(generateSecret.getAlgorithm()); cipher.init(1, generateSecret); byte[] doFinal = cipher.doFinal(str2.getBytes(CHARSET)); return android.util.Base64.encodeToString(doFinal, 0, doFinal.length, 2); } catch (UnsupportedEncodingException | InvalidKeyException | NoSuchAlgorithmException | InvalidKeySpecException | BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException unused) { return ""; } }


str密钥 str2是密码
可以看输出数据加密使用的是3DES进行加密,也就是128位168位的二进制数据进行加密,这里使用的是24位3个字节数据来存储密码。

写一个脚本进行解密代码如下:

[Python] 纯文本查看 复制代码

from Crypto.Cipher import DES3 from Crypto.Util.Padding import unpad import base64 def decrypt_3des_ecb(encrypted_base64: str, key_str: str) -> str: """ 解密使用Java函数加密的3DES-ECB数据 参数: encrypted_base64: Base64编码的加密字符串 key_str: 密钥字符串(长度不足24字节时用0填充) 返回: 解密后的原始字符串 """ # 处理密钥(与Java逻辑一致) key_bytes = key_str.encode('utf-8') if len(key_bytes) < 24: # 不足24字节时用0填充 key_bytes = key_bytes.ljust(24, b'\x00') else: # 超过24字节时截取前24字节 key_bytes = key_bytes[:24] # 解码Base64密文 encrypted_bytes = base64.b64decode(encrypted_base64) # 创建3DES-ECB解密器 cipher = DES3.new(key_bytes, DES3.MODE_ECB) # 解密并去除PKCS5填充 decrypted_bytes = cipher.decrypt(encrypted_bytes) unpadded_bytes = unpad(decrypted_bytes, DES3.block_size) # 假设原始编码是UTF-8(根据Java代码中的CHARSET) return unpadded_bytes.decode('utf-8') if __name__ == "__main__": encrypted_data = "xxxxxxxxxxxx" # 替换为实际加密数据 # 密钥(与加密时使用的相同) key = "bc52a640-7bda-49d8-9a25-" # 长度不足24字节 decrypted_text = decrypt_3des_ecb(encrypted_data, key) print(f"Decrypted Text: {decrypted_text}")




最终得到解密的原始协议数据





 

免费评分 参与人数 3威望 +1 吾爱币 +22 热心值 +3 理由

xlt12580
    + 1   + 1   热心回复!  

4everlove
    + 1   + 1   用心讨论,共获提升!  

正己
  + 1   + 20   + 1   感谢发布原创作品,吾爱破解论坛因你更精彩!  

查看全部评分

首页
评论
分享
Top