热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Python反反爬虫Frida破解某安卓社区token反爬虫

前言不多逼逼,这个安卓社区就是酷安,以前想过要爬这软件,但是都忘了,几天前抓了下它的包,发现请求headers

前言

不多逼逼,这个安卓社区就是酷安,以前想过要爬这软件,但是都忘了,几天前抓了下它的包,发现请求 headers 里有一个 token 验证,果断就给破了

分析过程

先抓个包

640?wx_fmt=png

可以看到其中有个请求头 X-App-Token,这就是验证,至于X-App-Device这玩意儿应该是获取你手机信息的,不管它,先看看软件源代码,找到请求方法

1、jeb分析

没加固,好像也没混淆,舒服

640?wx_fmt=jpeg

搜索关键字:X-App-Token

640?wx_fmt=png

很明显找到了我们要的东西了,(jeb3.0按tab键反编译)

640?wx_fmt=png

这个 X-App-Token 是变量 v21,v21是,一个AuthUtils类里的getAS方法返回的

640?wx_fmt=jpeg

640?wx_fmt=jpeg

跟进可以发现这是一个native方法,lib是native-lib

640?wx_fmt=png

到这里就没法用jeb分析了,我们先看看参数2 deviceId 是个什么玩意儿

可以看到是一个 SystemUtils 类里的 getDeviceID方法返回的,传入一个context参数

640?wx_fmt=png

我们去app的application hook这个方法,在里面输出一下这个device id

640?wx_fmt=png

我找到一个代码量最少的方法,这样可以帮助我们不破坏原逻辑的情况下hook

640?wx_fmt=png

hook的代码很简单

Java.perform(function() { var CoolMarket = Java.use('com.coolapk.market.CoolMarketApplication'); CoolMarket.onLog.implementation = function() { var deviceId = Java.use('com.coolapk.market.util.SystemUtils').getDeviceId(this); console.log('Device Id: ', deviceId); var app_token = Java.use('com.coolapk.market.util.AuthUtils').getAS(this, deviceId); console.log('App Token: ', app_token); console.log('----------'); return 1; }
})

拿到 deviceId 后分析so

ida分析

解压apk拿到 native-lib.so,用ida打开

640?wx_fmt=png

我们已知方法名和参数个数,那么就先搜索方法名

Function Window 按 option+t 搜索 getAS,可以看到,毛都没有

640?wx_fmt=png

640?wx_fmt=png

那我们就到 IDA View 里搜索,快捷键一样

640?wx_fmt=png

找到了这个,参数是两个,但这个不是方法,没法 F5 反编译

640?wx_fmt=png

 不瞒你们,这个DCB是个什么玩意儿我也不知道.

但是我在 Function Window 瞎翻想找一些我能看得懂的方法名时看到了这个

640?wx_fmt=png

我一看,这个方法好像和 getAS 有点关系就顺手 F5 了

简单看了看里面的代码,我估计我要找的是这个方法,为什么是这个方法而不是其他的

我分析了 X-App-Token 这个验证的组成,它长这样:f2c29a109fde487e9350d3e6b881036a8513efac-09ea-3709-b214-95b366f1a1850x5d024391

我之前就获取到了我的 Device Id,我无意间看到了我的 device id就在里面,然后我把它拆分成了这样:

  • f2c29a109fde487e9350d3e6b881036a

  • 8513efac-09ea-3709-b214-95b366f1a185

  • 0x5d024391

第一项很明显是md5密文,第二部分就是device id,最后是一个十六进制,不知道什么玩意儿,但我在那个 getAuthString 代码里看到了这段

640?wx_fmt=png

这是一个字符串拼接的过程,其中 v82 是md5密文,也是字符串的头部,后面接着是 v43(device id)、字符串0x、最后是 hex_time (这是我改了后的命名),所以我就能确定这个方法就是我想要的;

上面说的也就是接下来要分析的,其中那个十六进制的东西就是时间戳,我们只需要分析出md5是怎么来的就行了,我们知道md5是 v61,v61 的加密代码在这

640?wx_fmt=png

加密的内容是v58,v58是一个经过base64编码后的变量

640?wx_fmt=png

我懒得去看它是什么了,我直接hook了md5加密类,有三个方法,一个是md5(应该是构造方法把)、update、finalize,hook 代码如下

// 这里算基址是学的四哥的
var JNI_LOAD_POINTER = Module.getExportByName('libnative-lib.so', 'JNI_OnLoad'); // 首先拿到 JNI_OnLoad方法的地址
// 这里减去的是从so中得到的JNI_OnLoad的地址 0x31A04
var BASE_ADDR = parseInt(JNI_LOAD_POINTER) - parseInt('0x31A04'); // 用程序运行中JNI_OnLoad的绝对地址减去它的相对地址得到基址
// MD5::MD5
Java.perform(function() { // 然后用基址 + 要hook的方法的相对地址就得到了绝对地址 var hookpointer = '0x' + parseInt(BASE_ADDR + parseInt('0x32168')).toString(16) // 获取要hook方法的地址 var pointer = new NativePointer(hookpointer) // 根据方法地址构建NativePointer console.log('[MD5::MD5] hook pointer: ', pointer) var arg0, arg1, arg2, arg3 Interceptor.attach(pointer, { onEnter: function(args) { arg0 = args[0] arg1 = args[1] console.log('\n') console.log('=====> [MD5::MD5] -> [方法调用前]') console.log('参数1: {0} => {1}'.format(arg0, Memory.readCString(arg0))) // Memory.readCString 是读取地址为字符串,类似的还有readUtf8String、readUtf16String等 console.log('参数2: {0} => {1}'.format(arg1, Memory.readCString(arg1))) console.log('\n') }, onLeave: function(retval) { console.log('\n') console.log('=====> [MD5::MD5] -> [方法调用后]:') console.log('返回值: ', retval) console.log('参数1: {0} => {1}'.format(arg0, Memory.readCString(arg0))) console.log('参数2: {0} => {1}'.format(arg1, Memory.readCString(arg1))) console.log('\n') } } )
})
// MD5::update
Java.perform(function() { var hookpointer = '0x' + parseInt(BASE_ADDR + parseInt('0x329AC')).toString(16) // 获取要hook方法的地址 var pointer = new NativePointer(hookpointer) // 根据方法地址构建NativePointer console.log('[MD5::update] hook pointer: ', pointer) var arg0, arg1, arg2, arg3 Interceptor.attach(pointer, { onEnter: function(args) { arg0 = args[0] arg1 = args[1] arg2 = args[2] console.log('\n') console.log('=====> [MD5::update] -> [方法调用前]') console.log('参数1: {0} => {1}'.format(arg0, Memory.readCString(arg0))) console.log('参数2: {0} => {1}'.format(arg1, Memory.readCString(arg1))) console.log('参数3: {0} => {1}'.format(arg2, Memory.readCString(arg2))) console.log('\n') }, onLeave: function(retval) { console.log('\n') console.log('=====> [MD5::update] -> [方法调用后]:') console.log('返回值: ', retval) console.log('参数1: {0} => {1}'.format(arg0, Memory.readCString(arg0))) console.log('参数2: {0} => {1}'.format(arg1, Memory.readCString(arg1))) console.log('参数3: {0} => {1}'.format(arg2, Memory.readCString(arg2))) console.log('\n') } } )
})
// MD5::finalize
Java.perform(function() { var hookpointer = '0x' + parseInt(BASE_ADDR + parseInt('0x321C4')).toString(16) // 获取要hook方法的地址 var pointer = new NativePointer(hookpointer) // 根据方法地址构建NativePointer console.log('[MD5::finalize] hook pointer: ', pointer) var arg0, arg1, arg2, arg3 Interceptor.attach(pointer, { onEnter: function(args) { arg0 = args[0] arg1 = args[1] arg2 = args[2] arg3 = args[3] console.log('\n') console.log('=====> [MD5::finalize] -> [方法调用前]') console.log('参数1: {0} => {1}'.format(arg0, Memory.readCString(arg0))) console.log('参数2: {0} => {1}'.format(arg1, Memory.readCString(arg1))) console.log('参数3: {0} => {1}'.format(arg2, Memory.readCString(arg2))) console.log('参数4: {0} => {1}'.format(arg3, Memory.readCString(arg3))) console.log('\n') }, onLeave: function(retval) { console.log('\n') console.log('=====> [MD5::finalize] -> [方法调用后]:') console.log('返回值: ', retval) console.log('参数1: {0} => {1}'.format(arg0, Memory.readCString(arg0))) console.log('参数2: {0} => {1}'.format(arg1, Memory.readCString(arg1))) console.log('参数3: {0} => {1}'.format(arg2, Memory.readCString(arg2))) console.log('参数4: {0} => {1}'.format(arg3, Memory.readCString(arg3))) console.log('\n') } } )
})

运行后得到了那个base64编码过的内容:

dG9rZW46Ly9jb20uY29vbGFway5tYXJrZXQvYzY3ZWY1OTQzNzg0ZDA5NzUwZGNmYmIzMTAyMGYwYWI/MzgyMzIxNWQ5MWQyOWQ5ODg3ZWJjMDVmMGQ3ZmQzMGQkODUxM2VmYWMtMDllYS0zNzA5LWIyMTQtOTViMzY2ZjFhMTg1JmNvbS5jb29sYXBrLm1hcmtldA==

经过解码后:

token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab?3823215d91d29d9887ebc05f0d7fd30d$8513efac-09ea-3709-b214-95b366f1a185&com.coolapk.market

在我看到这段代码后,注:这里的第三个注释里说的猜测是我的错,我一开始分析的时候因为不知道那个字符串是固定的就以为是"?",现在实锤了,截图的时候忘了改

640?wx_fmt=png

上面解码后的内容可以拆分为:

token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab?3823215d91d29d9887ebc05f0d7fd30d

$

8513efac-09ea-3709-b214-95b366f1a185

&

com.coolapk.market

据我分析,只需要的到第二部分的md5加密的来历就行了,继续分析,找到了加密的地方

640?wx_fmt=png

根据这图的画线,可以明确的知道这md5就是时间戳,在我hook的输出中也可以看到这个就是时间戳

640?wx_fmt=png

640?wx_fmt=png

至于 token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab? 这个是不变的

结论

token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab? + md5加密后的时间戳 + $ + device id + & + com.coolapk.market(包名),将其md5加密后得到 第一部分

token的来历就是:第一部分 + deivce id + 0x + 十六进制转换后的时间戳

简单的测试代码:

import requests
import time
import hashlib
import base64
DEVICE_ID = "8513efac-09ea-3709-b214-95b366f1a185"
def get_app_token(): t = int(time.time()) hex_t = hex(t) # 时间戳加密 md5_t = hashlib.md5(str(t).encode('utf-8')).hexdigest() # 不知道什么鬼字符串拼接 a = 'token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab?{}${}&com.coolapk.market' \ .format(md5_t, DEVICE_ID) # 不知道什么鬼字符串拼接 后的字符串再次加密 md5_a = hashlib.md5(base64.b64encode(a.encode('utf-8'))).hexdigest() token = '{}{}{}'.format(md5_a, DEVICE_ID, hex_t) print(token) return token
def request(): url = "https://api.coolapk.com/v6/main/indexV8?page=1" headers = { "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 9; MI 8 SE MIUI/9.5.9) (#Build; Xiaomi; MI 8 SE; PKQ1.181121.001; 9) +CoolMarket/9.2.2-1905301" } headers = { "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 9; MI 8 SE MIUI/9.5.9) (#Build; Xiaomi; MI 8 SE; PKQ1.181121.001; 9) +CoolMarket/9.2.2-1905301", "X-App-Id": "com.coolapk.market", "X-Requested-With": "XMLHttpRequest", "X-Sdk-Int": "28", "X-Sdk-Locale": "zh-CN", "X-Api-Version": "9", "X-App-Version": "9.2.2", "X-App-Code": "1903501", "X-App-Device": "QRTBCOgkUTgsTat9WYphFI7kWbvFWaYByO1YjOCdjOxAjOxEkOFJjODlDI7ATNxMjM5MTOxcjMwAjN0AyOxEjNwgDNxITM2kDMzcTOgsTZzkTZlJ2MwUDNhJ2MyYzM", "Host": "api.coolapk.com", "X-Dark-Mode": "0", "X-App-Token": get_app_token(), } resp = requests.get(url, headers=headers) print(resp.text)
if __name__ == '__main__': request()

最后

代码啥的都提交到Github了,CoolapkTokenCrack



推荐阅读
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 本文介绍了一个React Native新手在尝试将数据发布到服务器时遇到的问题,以及他的React Native代码和服务器端代码。他使用fetch方法将数据发送到服务器,但无法在服务器端读取/获取发布的数据。 ... [详细]
  • 超级简单加解密工具的方案和功能
    本文介绍了一个超级简单的加解密工具的方案和功能。该工具可以读取文件头,并根据特定长度进行加密,加密后将加密部分写入源文件。同时,该工具也支持解密操作。加密和解密过程是可逆的。本文还提到了一些相关的功能和使用方法,并给出了Python代码示例。 ... [详细]
  • 本文详细介绍了Android中的坐标系以及与View相关的方法。首先介绍了Android坐标系和视图坐标系的概念,并通过图示进行了解释。接着提到了View的大小可以超过手机屏幕,并且只有在手机屏幕内才能看到。最后,作者表示将在后续文章中继续探讨与View相关的内容。 ... [详细]
  • mysqldinitializeconsole失败_mysql03误删除了所有用户解决办法
    误删除了所有用户解决办法第一种方法(企业常用)1.将数据库down掉[rootdb03mysql]#etcinit.dmysqldstopShuttingdownMySQL..SU ... [详细]
  • 今天就跟大家聊聊有关怎么在Android应用中实现一个换肤功能,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根 ... [详细]
  • 近来有一个需求,是需要在androidjava基础库中插入一些log信息,完成这个工作需要的前置条件有编译好的android源码具体android源码如何编译,这 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 本文介绍了在Vue项目中如何结合Element UI解决连续上传多张图片及图片编辑的问题。作者强调了在编码前要明确需求和所需要的结果,并详细描述了自己的代码实现过程。 ... [详细]
  • 本文介绍了NetCore WebAPI开发的探索过程,包括新建项目、运行接口获取数据、跨平台部署等。同时还提供了客户端访问代码示例,包括Post函数、服务器post地址、api参数等。详细讲解了部署模式选择、框架依赖和独立部署的区别,以及在Windows和Linux平台上的部署方法。 ... [详细]
author-avatar
秦schueler
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有