验证码服务平台 
谷歌人机 
ReCaptcha(v2/v3 通用版), 直接返回 token
ReCaptcha(v2/v3 企业版), 直接返回 token
ReCaptcha(steam), 直接返回 token
CloudFlare 盾通用版, 返回 cf_clearance、__cf_bm 、正确响应源码 html、验证流程使用的 tls 指纹
Incapsula 盾 reese84 通用版, 返回 reese84 cookie 或 solution 参数
Incapsula 盾 __utmvc 通用版, 服务器直接无感验证 或 __utmvc cookie
Hcaptcha 通用版, 直接返回 generated_pass_UUID
直接返回 _abck
tls 转发接口, 针对校验 ja3、http2 等指纹(akamai/cloudflare)的接口
更多支持后续敬请期待..... 
注册连接:http://c.nxw.so/Aqi 
新用户注册加群即送10000点余额 
新用户注册加群即送10000点余额 
新用户注册加群即送10000点余额

1.png

工欲善其事必先利其器

工具:真机or模拟器

  Fiddler 或任意一款抓包工具
  VSCode
  frida


老规矩 先抓包
1.png

其中 zzReqSign 便是我们需要还原的算法 我们查课发现没有加固 直接丢进jadx进行反编译
搜索定位 我们来到如下位置
2.png

发现是个 native 方法 那么加密在so层 上面 System.loadLibrary("signLib"); 就是加载so的指令 其中 signLib就是so名字

解压apk 去lib目录寻找是否有这个文件
3.png

拖入IDA 导出函数 搜索 getSign

定位如下位置:
4.png

代码如下:

int __fastcall Java_com_zhuanzhuan_sign_SignUtil_getSign(JNIEnv_ *a1, int a2, int a3, int a4)
{
  const char *app_sign_sha1; // r0
  struct _jobject *v9; // r8
  signed int v10; // r5
  int DevID; // r0
  struct _jobject *v12; // r8
  int v13; // r9
  jbyte *v14; // r6
  jsize v15; // r10
  _BYTE *v16; // r8
  signed int i; // r11
  jbyte v18; // r4
  int v19; // r1
  JNIEnv_ *v20; // r4
  int v21; // r9
  int MD5; // r10
  int v24; // [sp+8h] [bp-30h]
  struct _jobject *v25; // [sp+Ch] [bp-2Ch]
  struct _jobject *v26; // [sp+10h] [bp-28h]
  JNIEnv_ *v27; // [sp+14h] [bp-24h]
  jbyte *v28; // [sp+18h] [bp-20h]

  if ( !a3 )
    return 0;
  app_sign_sha1 = get_app_sign_sha1(a1, a4);
  if ( !app_sign_sha1 )
    return 0;
  if ( strcmp(app_sign_sha1, app_sing_str) )
    return 0;
  v9 = toBytes(a1, a3);
  v10 = a1->functions->GetArrayLength(a1, v9);
  v28 = a1->functions->GetByteArrayElements(a1, v9, 0);
  DevID = getDevID(a1, a2);
  if ( !DevID )
    return 0;
  v26 = v9;
  v24 = DevID;
  v12 = toBytes(a1, DevID);
  v13 = 0;
  v14 = a1->functions->GetByteArrayElements(a1, v12, 0);
  v27 = a1;
  v25 = v12;
  v15 = a1->functions->GetArrayLength(a1, v12);
  v16 = malloc(v10);
  for ( i = 0; i < v10; ++i )
  {
    v18 = v28[v13 % v10];
    v19 = (v13 + 1) % v15;
    v13 += 2;
    v16[i] = v14[v19] ^ v18;
  }
  v20 = v27;
  v21 = v27->functions->NewByteArray(&v27->functions, v10 + 9);
  (v20->functions->SetByteArrayRegion)(v20, v21, 0, v10, v16, a3);
  v20->functions->SetByteArrayRegion(&v20->functions, v21, v10, 9, "smiletozz");
  MD5 = getMD5(v27, v21);
  v20->functions->DeleteLocalRef(&v20->functions, v21);
  free(v16);
  v20->functions->DeleteLocalRef(&v20->functions, v24);
  v20->functions->ReleaseByteArrayElements(&v20->functions, v25, v14, 0);
  v20->functions->ReleaseByteArrayElements(&v20->functions, v26, v28, 0);
  v20->functions->DeleteLocalRef(&v20->functions, v25);
  v20->functions->DeleteLocalRef(&v20->functions, v26);
  return MD5;

这段函数最终返回了MD5 MD5是由 MD5 = getMD5(v27, v21);而来的 我们姑且认为他是一个md5算法 点进去看看
5.png

代码如下:

int __fastcall getDigestedBytes(_JNIEnv *a1, int a2)
{
  struct _jobject *v4; // r6
  jmethodID v5; // r5
  jstring v6; // r0
  _jobject *v7; // r9
  _jmethodID *v8; // r0
  int v9; // r8

  v4 = a1->functions->FindClass(a1, "java/security/MessageDigest");
  v5 = a1->functions->GetStaticMethodID(a1, v4, "getInstance", "(Ljava/lang/String;)Ljava/security/MessageDigest;");
  v6 = a1->functions->NewStringUTF(a1, "MD5");
  v7 = _JNIEnv::CallStaticObjectMethod(a1, v4, v5, v6);
  v8 = a1->functions->GetMethodID(a1, v4, "digest", "([B)[B");
  v9 = _JNIEnv::CallObjectMethod(a1, v7, v8, a2);
  a1->functions->DeleteLocalRef(a1, v4);
  a1->functions->DeleteLocalRef(a1, v7);
  return v9;

这段就比较简单了 反射java调用md5算法 到这里好像已经分析完毕 貌似就是个md5算法而已 是不是这样呢?我们试验一下

frida hook java层 getSign方法
代码如下:

let SignUtil = Java.use("com.zhuanzhuan.sign.SignUtil");
SignUtil["getSign"].overload('java.lang.String', 'android.content.Context').implementation = function (str, context) {
    console.log(`SignUtil.getSign is called: str=${str}, context=${context}`);
    let result = this["getSign"](str, context);
    console.log(`SignUtil.getSign result=${result}`);
    return result;
};

抓包结果为:zzReqSign: 47383db507e87badc5bc9ce348a6193e

我们去hook结果搜索
6.png

下面为结果 上面是明文 我们md5一下
7.png

发现结果并不对 那么肯定是在md5之前还有其他操作 我们返回so继续分析
8.png

getDevID 代码如下:

int __fastcall getDevID(int a1, int a2)
{
  int v4; // r2

  v4 = (*(*a1 + 452))(a1, a2, "getDeviceId", "()Ljava/lang/String;");
  return j__JNIEnv::CallStaticObjectMethod(a1, a2, v4);
}

这里对应的好像就是java 层
10.png

我们hook一下此方法 拿到值

let SignUtil = Java.use("com.zhuanzhuan.sign.SignUtil");
SignUtil["getDeviceId"].implementation = function () {
    console.log(`SignUtil.getDeviceId is called`);
    let result = this["getDeviceId"]();
    console.log(`SignUtil.getDeviceId result=${result}`);
    return result;
};

11.png

值=SignUtil.getDeviceId result=BE304BAE68E9C99571DEE26718102680

12.png

到此分析完毕

算法还原
13.png

最后修改:2023 年 07 月 26 日
如果觉得我的文章对你有用,请随意赞赏