某英语听力APP字幕编码方式逆向

admin 2025年1月1日11:39:31评论10 views字数 6581阅读21分56秒阅读模式

声明

仅用于学习交流,禁止用于商业用途。

使用工具

  • Nexus 6P 真机 Android 8.0
  • 抓包工具Charles或Fiddler
  • hook工具 Frida https://frida.re/
  • 反编译 jadx https://github.com/skylot/jadx
  • ida(可选)

APP原包及APP脱壳包下载

https://pan.quark.cn/s/0242e2e2ce54

某英语听力APP字幕编码方式逆向

抓包前准备

安卓7.0以上,系统不再信任用户证书,因此需要将用户证书转系统证书

用户证书存放目录:

 复制代码 隐藏代码/data/misc/user/0/cacerts-added

系统证书存放目录:

 复制代码 隐藏代码/system/etc/security/cacerts

使用mv命令或者MT管理器,将用户证书移动到系统证书目录下即可:

 复制代码 隐藏代码mv 87bc3517.0 /system/etc/security/cacerts

抓包

发现这个请求比较像我们需要找的字幕数据,因为其中有subtitle

 复制代码 隐藏代码curl --location 'https://脱敏/api/v5/ting/subtitle/a435138e-761c-11ef-8108-005056866eda?ts=6&ta=35' --header 'Host: api.frdic.com' --header 'authorization: QYN eyJ1c2脱敏joidHJhbnNBfCJ9' --header 'user-agent: /脱敏_en_android/10.0.7/619a812010d90f6a///' --header 'eudicuseragent: /脱敏_en_android/10.0.7/619a812010d90f6a///' --header 'eudictimezone8'

可是响应的数据为byte类型,需要搞清楚编码方式

某英语听力APP字幕编码方式逆向

frida hook前准备工作

这个App是有进行frida检测的,因此需要过掉他的检测,《安卓逆向这档事》十八、表哥,你也不想你的Frida被检测吧!(上)https://www.52pojie.cn/thread-1921073-1-1.html

或者直接使用去特征的魔改fridahttps://github.com/hzzheyang/strongR-frida-android

java层加密方式一把梭

不知道是什么加密解密方式,先用java层加密方式一把梭的frida hook脚本试一下。

 复制代码 隐藏代码https://blog.csdn.net/rni88/article/details/134364285

虽然确实hook到不少加密算法,但是经过一番尝试没有hook到处理字幕编码的算法

静态分析

发现这个App是加壳的,需要先脱壳就可以分析了。

搜索subtitle找到以下类

某英语听力APP字幕编码方式逆向

这个类查找用例

某英语听力APP字幕编码方式逆向

找到处理请求结果的函数

某英语听力APP字幕编码方式逆向

找到这个反序列化数据的函数

某英语听力APP字幕编码方式逆向

不过可惜的是个jni函数,实现过程在so层

某英语听力APP字幕编码方式逆向

先用frida hook验证以下。

直接hook deserializeData这个函数也行,不过因为参数和返回值都是byte数组所以不方便看。

我就直接hook上一层的readTree方法,发现没有问题,可以得到解密的数据。

letObjectMapper = Java.use("com.fasterxml.jackson.databind.ObjectMapper");ObjectMapper["readTree"].overload('[B').implementation = function (bArr) {console.log(`ObjectMapper.readTree is called: bArr=${bArr}`);let result = this["readTree"](bArr);send(`ObjectMapper.readTree result=${result}`);return result;    };
某英语听力APP字幕编码方式逆向

去SO层看一眼

某英语听力APP字幕编码方式逆向

发现加载的是这个so文件

使用ida分析一下

  • 静态注册,在导出函数里面搜一下,没有发现java_XX_deserializeData这个函数
  • 动态注册,hook jni函数动态注册,找到了这个函数

发现这个函数就叫deserializeData

 复制代码 隐藏代码int __fastcall deserializeData(int a1, int a2, int a3){ int v5; // r8  unsigned int v6; // r0 int v7; // r6 int v8; // r1  char v9; // r0 int v10; // r1 int v11; // r5  _BYTE *v12; // r0 int v13; // r3  unsigned __int8 v15; // [sp+14h] [bp-3Ch] BYREF  _BYTE v16[3]; // [sp+15h] [bp-3Bh] BYREF int v17; // [sp+18h] [bp-38h]  void *v18; // [sp+1Ch] [bp-34h] int v19; // [sp+20h] [bp-30h] BYREF int v20; // [sp+24h] [bp-2Ch]  void *v21; // [sp+28h] [bp-28h]  char v22; // [sp+2Fh] [bp-21h] BYREF  v22 = 0;  v5 = (*(int (__fastcall **)(int, int, char *))(*(_DWORD *)a1 + 736))(a1, a3, &v22);  v6 = (*(int (__fastcall **)(int, int))(*(_DWORD *)a1 + 684))(a1, a3);  v7 = v6;  v19 = 0;  v20 = 0;  v21 = 0; if ( v6 <= 0xA )  { if ( v6 )      _memmove_chk((char *)&v19 + 1, v5, v6, 11);    *((_BYTE *)&v19 + v7 + 1) = 0; if ( (unsigned __int8)v19 << 31 )      v20 = v7;    else      LOBYTE(v19) = 2 * v7;  }  else  {    std::string::__grow_by_and_replace(&v19, 10, v6 - 10, 0, 0, 0, v6, v5);  }  v8 = (unsigned __int8)byte_5822A4;  __dmb(0xBu); if ( v8 << 31 || !_cxa_guard_acquire((__guard *)&byte_5822A4) )  {    v9 = byte_582342; if ( !byte_582342 ) goto LABEL_11; goto LABEL_10;  }  dword_58233C = 438115359;  byte_582342 = 46;  word_582340 = 6171;  _cxa_atexit(    (void (__fastcall *)(void *))ay::obfuscated_data<7u,(char)46>::~obfuscated_data,    &dword_58233C,    &off_54F480);  _cxa_guard_release((__guard *)&byte_5822A4);  v9 = byte_582342; if ( byte_582342 )  {LABEL_10:    byte_582342 = v9 ^ 0x2E;    LOBYTE(dword_58233C) = dword_58233C ^ 0x2E;    BYTE1(dword_58233C) ^= 0x2Eu;    BYTE2(dword_58233C) ^= 0x2Eu;    HIBYTE(dword_58233C) ^= 0x2Eu;    LOBYTE(word_582340) = word_582340 ^ 0x2E;    HIBYTE(word_582340) ^= 0x2Eu;  }LABEL_11:  EuDataBase::StrOpt::decompress_string(&v15, &v19, &dword_58233C);  (*(void (__fastcall **)(int, int, int, int))(*(_DWORD *)a1 + 768))(a1, a3, v5, 2);  v10 = v17; if ( !(v15 << 31) )    v10 = v15 >> 1;  v11 = (*(int (__fastcall **)(int, int))(*(_DWORD *)a1 + 704))(a1, v10);  v13 = v17;  v12 = v18; if ( (v15 & 1) == 0 )    v12 = v16; if ( (v15 & 1) == 0 )    v13 = v15 >> 1;  (*(void (__fastcall **)(int, int, _DWORD, int, _BYTE *))(*(_DWORD *)a1 + 832))(a1, v11, 0, v13, v12); if ( v15 << 31 )    operator delete(v18); if ( (unsigned __int8)v19 << 31 )    operator delete(v21); return v11;}

尝试用chatGPT改写成python代码失败。

RPC调用

虽然不能还原算法,不过可以用frida rpc调用以下

 复制代码 隐藏代码rpc.exports = {deserializefunction (param_b) {var result = ''//工具相关函数var base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',            base64DecodeChars = newArray((-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), 62, (-1), (-1), (-1), 6352535455565758596061, (-1), (-1), (-1), (-1), (-1), (-1), (-1), 012345678910111213141516171819202122232425, (-1), (-1), (-1), (-1), (-1), (-1), 2627282930313233343536373839404142434445464748495051, (-1), (-1), (-1), (-1), (-1));functionbase64ToBytes(e) {var r, a, c, h, o, t, d;for (t = e.length, o = 0, d = []; o < t;) {do                    r = base64DecodeChars[255 & e.charCodeAt(o++)];while (o < t && r == -1);if (r == -1)break;do                    a = base64DecodeChars[255 & e.charCodeAt(o++)];while (o < t && a == -1);if (a == -1)break;                d.push(r << 2 | (48 & a) >> 4);do {if (c = 255 & e.charCodeAt(o++), 61 == c)return d;                    c = base64DecodeChars[c]                } while (o < t && c == -1);if (c == -1)break;                d.push((15 & a) << 4 | (60 & c) >> 2);do {if (h = 255 & e.charCodeAt(o++), 61 == h)return d;                    h = base64DecodeChars[h]                } while (o < t && h == -1);if (h == -1)break;                d.push((3 & c) << 6 | h)            }return d        }Java.perform(function () {var cls = Java.use('com.eusoft.dict.util.JniApi');var obj = cls.$new();varObjectMapper = Java.use('com.fasterxml.jackson.databind.ObjectMapper');var my_objectMapper = ObjectMapper.$new();var javaBytes = Java.array('byte'base64ToBytes(param_b)); // 巨坑,转成javaBytes才可以传入jni函数,Java.array('byte', jsBytes) 创建了一个与 byte[] 类型匹配的 Java 数组。            result = obj['deserializeData'](javaBytes)            result = my_objectMapper.readTree(result)varJsonNode = Java.use('com.fasterxml.jackson.databind.JsonNode')            result = Java.cast(result, JsonNode);        });return result.toString()    }

核心代码就

 复制代码 隐藏代码Java.perform(function () {var cls = Java.use('com.eusoft.dict.util.JniApi');var obj = cls.$new();varObjectMapper = Java.use('com.fasterxml.jackson.databind.ObjectMapper');var my_objectMapper = ObjectMapper.$new();var javaBytes = Java.array('byte'base64ToBytes(param_b)); // 巨坑,转成javaBytes才可以传入jni函数,Java.array('byte', jsBytes) 创建了一个与 byte[] 类型匹配的 Java 数组。            result = obj['deserializeData'](javaBytes)            result = my_objectMapper.readTree(result)varJsonNode = Java.use('com.fasterxml.jackson.databind.JsonNode')            result = Java.cast(result, JsonNode);        });

有一个巨坑的点,因为参数是byte数组,所以需要用var javaBytes = Java.array('byte', base64ToBytes(param_b))转一下,否则就会报错argument types do not match any of:nt.overload('[B')

某英语听力APP字幕编码方式逆向

· 今 日 推 荐 ·

某英语听力APP字幕编码方式逆向

本文内容来自网络,如有侵权请联系删除

原文始发于微信公众号(逆向有你):某英语听力APP字幕编码方式逆向

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月1日11:39:31
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   某英语听力APP字幕编码方式逆向https://cn-sec.com/archives/3579171.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息