违法应用移动TV取证分析

篇幅有限

完整内容及源码关注公众号:ReverseCode,发送

抓包

应用层抓包

img

传输层抓包

charles开启Enable socks proxy

wget https://www.charlesproxy.com/assets/release/4.6.1/charles-proxy-4.6.1_amd64.tar.gz?k=17bcbd3dc2

tar zxf charles-proxy-4.6.1_amd64.tar.gz && ./charles 通过注册码注册或生成加权jar包破解

vim ~/.zshrc 并source ~/.zshrc 使得charles在任意路径可启动

1
export PATH="/root/Android/Sdk/ndk-bundle:/root/Android/Sdk/platform-tools:${JAVA_HOME}/bin:$PATH:/root/Desktop/charles/bin:/root/Desktop/jadx-1.2.0/bin"

htop 查看破解情况

电脑:192.168.0.106

手机:192.168.0.102

虚拟机:192.168.0.107

虚拟网络编辑器选择获取ip的网卡,保证这三台机器在同一局域网内且互相ping通。

配置charles

socks proxy工作于传输层,更好的观察应用层协议和socks抓包。

image-20210422211129237

开启ssl

image-20210422211216544

postern

adb install 0714com.tunnelworkshop.postern_2018-10-07.apk

QtScrcpy 设置投屏

image-20210422211526433

配置socks5抓包代理

image-20210422211731524

配置socks5抓包配置规则

image-20210422211826284

打开socks vpn连接虚拟机抓包,虚拟机收到连接请求后点击Allow

image-20210422212010520

在手机浏览器输入地址 chls.pro/ssl 或者 charlesproxy.com/getssl ,出现证书安装页面,点击安装,如果依旧app抓不到,需要把个人证书放到系统根目录

Android8

1
2
3
4
5
cd /data/misc/user/0/cacerts-added/
mount -o remount,rw /
chmod 777 *
cp * /etc/security/cacerts/
mount -o remount,ro /

Android 7

1
2
3
4
5
cd /data/misc/user/0/cacerts-added/
mount -o rw,remount /system
chmod 777 *
cp * /etc/security/cacerts/
mount -o ro,remount /system

movetv分析

1
2
3
4
./fs1428arm64 
pyenv local 3.8.5
frida -UF -l hookSocket.js -o moveTV.txt attach方式hook登录抓包,基于socks层抓包无法对抗,除非做了VPN检测
frida -U -f com.cz.babySister -l hookSocket.js -o moveTV.txt spawn方式hook,输入%resume重新启动,或者直接在命令后加--no-pause

image-20210422213451714

jadx-gui movetv.apk 已经加壳一个Activity都找不到

image-20210422214547118

脱壳

FRIDA-DEXDump

git clone https://github.com/hluwa/FRIDA-DEXDump.git 启动app放在前台

cd ~/Desktop/FRIDA-DEXDump/frida_dexdump && python main.py 开始脱壳

image-20210422220211650

1
2
3
4
android hooking list activities  查看所有Activity
grep -ril "MainActivity" * 从脱下的dex中查找MainActivity
grep -ril "LoginActivity" *
jadx-gui com.cz.babySister/0x748d44201c.dex

image-20210422220645424

登录

抓登录包获取memi1字段其实来源于android-id

image-20210423013934848

android hooking watch class com.cz.babySister.activity.LoginActivity –dump-args –dump-backtrace –dump-return hook类中所有方法

image-20210423014732415

android hooking watch class_method com.cz.babySister.activity.LoginActivity.b –dump-args –dump-backtrace –dump-return hook登录b方法

image-20210423014946081

进入b方法中调用的RunnableC0042q类中

image-20210423015105146

image-20210423015154139

android hooking watch class_method com.cz.babySister.c.a.a –dump-args –dump-backtrace –dump-return hook方法a

image-20210423015411937

1
2
3
4
5
objection -g com.cz.babySister explore
android hooking search classes Settings
android hooking list class_methods android.provider.Settings
android hooking list class_methods android.provider.Settings$Secure
android hooking watch class_method android.provider.Settings$Secure.getString --dump-args --dump-backtrace --dump-return

image-20210423013626273

通过hook获取返回的结果50463fa80244d95f和chales中抓包的memi1完全一致

image-20210423014359588

注册

android hooking watch class_method com.cz.babySister.c.a.a –dump-args –dump-backtrace –dump-return

image-20210423015832197

对比抓包结果

image-20210423015906716

取证实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import base64
import time

import requests
requests.packages.urllib3.disable_warnings()

class tv:
def __init__(self):
self.root = 'http://39.108.64.125/WebRoot/superMaster/Server'
self.memi1 = "50463fa80244d95f"
self.rightkey = "376035775"
self.key = "308202d5308201bda00302010202041669d9bf300d06092a864886f70d01010b0500301b310b3009060355040613023836310c300a06035504031303776569301e170d3136303731383038313935395a170d3431303731323038313935395a301b310b3009060355040613023836310c300a0603550403130377656930820122300d06092a864886f70d01010105000382010f003082010a028201010095f85892400aae03ca4ed9dcd838d162290ae8dd51939aac6ecfde8282f207c4cd9e507929a279e0a36f1e4847330cb53908c92915b2c6a93d7064be452d073a472093f7ca14f4ab68f827582fe0988e9e4bc8a6ea3b56001cbbbb760f9eec571b0bbc97392e65aaf08c686f0e2ba353896d48a37c36716239977bd0e4dd878025cab497d8164537aec9f6599eefb98577dce972a1b794e211226520e23497beec3fd8548bb5b4d263120d40115cca28116bac32378df5033f536a0d7367fef78c587fefed28c5c9b35ba684ed6e46d9369c40950cf7ad7236d10b7a51dfd2a8f218db72323bbd19f46947410b1191f263012ad4ba8f749223e37591254ee7f50203010001a321301f301d0603551d0e041604143d43284bd5e4b0d322c9962a5b70aad4dcbc3634300d06092a864886f70d01010b050003820101000f04c51ff763311aa011777ba2842b441b15c316373d1e1ed4116cf86e29d55c6ed3fa4c475251b1fb4fac57195dbca0166ebe565d9834552a3758b97c4528bab1f7ab82bb3a9faa932f5bc10943f3daf52e0fe5889ffb58a6be67ea1c9a2fb37dc8aa6f3af476039a467336991a4e52dccd520195cd473eb5b984e702ed9ff638a14c3abb575a7a80ae4062084d1138a06a20e173be9df32df631311b07352898706198ddebaaa011f0da8e5f288f7cfb77505bc943f6476d6cc1feef56b68137aad91f23c4bb772169539d05653a6f0d75f7192164e822b934322f3a975df677903b1667f5dc1e9ddb185da3281d31bfb8f67a84bd23bbcb398f8bb637dd72"

def post(self, data=None):
if data is None:
data = {}
return requests.post(url=self.root, data=data)

def query(self, name, password):
ret = self.post({'name': name, 'pass': password})
print("query result is : ")
print(ret.content.decode('utf-8'))

def register(self, name, password):
ret = self.post({'name': name, 'pass': password, 'memi1': self.memi1,
'key': self.key, 'rightkey': self.rightkey, 'register': 'register'})
print("Register response data: ")
print(ret.content.decode('utf-8'))

def login(self, name, password):
ret = self.post({'name': name, 'pass': password, 'memi1': self.memi1,
'key': self.key, 'rightkey': self.rightkey, 'login': 'login'})
print("Login response data: ")
print(ret.content.decode('utf-8'))

def updateSocre(self, name, password, jifen):
t = int(round(time.time() * 1000))
sign = base64.b64encode(str(5 * t).encode('utf-8')).decode('utf-8')
ret = self.post({'name': name, 'pass': password,
'jifen': jifen, 'time': t, 'sign': sign})
print("UpdataScore response data: ")
print(ret.content.decode('utf-8'))


if __name__ == "__main__":
tv = tv()

# print(tv.query("eeeeffff", "gggghhhh"))

# 注册账号

print(tv.register("onejane3", "123456"))

# time.sleep(3)

# 登录账号
print(tv.login("onejane3", "123456"))


# 更新积分
# print(tv.updateSocre("mee4","mee4","1000"))

image-20210423020347585

Youpk

https://bbs.pediy.com/thread-259854.htm

linux平台下载最新的platform-tools刷机的时候,fastboot会报各种unknow command或接近的错误,把fastboot文件替换成以下随着aosp一起编译出来的即可:

fastboot_aosp7.1.zip

fastboot6.0.zip

fastboot8.1.0r1.zip

下载Youpk_v1.1

  1. 重启至bootloader: adb reboot bootloader
  2. 解压 Youpk_sailfish.zip 并双击 flash-all.bat,(尽量在kali上刷机,./flash-all.sh,因为windows会给我们的编程生涯带来80%的苦难)

adb install movetv.apk

adb shell “echo com.cz.babySister >> /data/local/tmp/unpacker.config”

  1. 启动apk等待脱壳,每隔10秒将自动重新脱壳(已完全dump的dex将被忽略), 当日志打印unpack end时脱壳完成

adb pull /data/data/com.cz.babySister/unpacker/

mv unpacker youpk/

  1. 调用修复工具 dexfixer.jar, 两个参数, 第一个为dump文件目录(必须为有效路径), 第二个为重组后的DEX目录(不存在将会创建)
    java -jar dexfixer.jar youpk/ youpk_out/
    
    jadx-gui _data_app_com.cz.babySister-1_base.apk_54276.dex  查看脱壳后的dex 
    

image-20210422224021639

使用场景

  1. 整体加固
  2. 抽取:

问题

  1. dump中途退出或卡死,重新启动进程,再次等待脱壳即可
  2. 当前仅支持被壳保护的dex, 不支持App动态加载的dex/jar

fart

脱抽取型壳并回填dex

安装新环境

vim /etc/proxychains4.conf 配置代理

1
2
socks5  192.168.0.106 1080
#http 127.0.0.1 12333

主机192.168.0.106 ssr开启运行局域网连接

image-20210423001429267

配置新环境

1
2
PYTHON_CONFIGURE_OPTS="--disable-ipv6" proxychains pyenv install 3.9.0  创建python3.9.0环境
PYTHON_CONFIGURE_OPTS="--disable-ipv6" proxychains pip install objection==1.9.5 安装objection 1.9.5套件包括frida-tools 9.2.4,frida 14.2.16

wget frida-server-14.2.16-android-arm64.xz

7z x frida-server-14.2.16-android-arm64.xz

1
2
3
4
5
adb push ~/Desktop/frida-server-14.2.16-android-arm64 /data/local/tmp/
adb shell
mv /data/local/tmp/frida-server-14.2.16-android-arm64 /data/local/tmp/fs1426arm64
chmod 777 /data/local/tmp/fs1426arm64
./data/local/tmp/fs1426arm64

事实证明,新版本不兼容还是用12.8.0的frida吧

调用fart

基于hook和反射脱壳,对比发现youpk最优秀。

adb reboot bootloader

cd ~/Desktop/sailfish-nzh54d 安卓8.0

./flash-all.sh 刷回去

root及基础配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
adb push Magisk-v20.4.zip /sdcard    
adb push magisk-riru-v21.3.zip /sdcard/Download 使用magisk模块安装并重启
adb push magisk-EdXposed-SandHook-v0.4.5.1_beta.4463.-release.zip /sdcard/Download 使用magisk模块安装并重启
adb install EdXposedManager-4.5.7-45700-org.meowcat.edxposed.manager-release.apk 安装xposed
adb push MagiskHidePropsConf-v5.3.4.zip /sdcard/Download
adb install JustTrustMePlus-debug.apk 用于结合xposed突破SSL Pinning抓包限制
adb reboot bootloader
fastboot boot twrp-3.4.0-0-sailfish.img 进入recovery mode
install Magisk-v20.4.zip
adb install MagiskManager-v7.5.1.apk 模块安装MagiskHidePropsConf,riru,EdXposed,
adb shell su通过Magisk获取root权限
settings put global captive_portal_http_url https://www.google.cn/generate_204 去除wifi上的×
settings put global captive_portal_https_url https://www.google.cn/generate_204
settings put global ntp_server 1.hk.pool.ntp.org 修改时区
props Edit MagiskHide props--ro.debuggable 设置全局可调试,getprop ro.debuggable 即可查看1,开启全局可调试
1
2
3
4
5
6
7
pyenv local 3.8.0
proxychains4 wget https://github.com/hanbinglengyue/FART.git
7z x frida_fart.zip && adb push lib/fart* /data/app && chmod 777 *.so 如果没有权限,放到/sdcard中再放入/data/app
cd Desktop/FART-master/frida_fart/ && frida -UF -l frida_fart_reflection.js attach模式启动
frida -U -f com.cz.babySister -l frida_fart_reflection.js --no-pause spawn模式启动
fart() 全量主动调用
frida -U -f com.cz.babySister -l frida_fart_hook.js --no-pause 基于安卓8,生成dex,hook连壳一起脱

image-20210422234838689

1
2
adb pull /sdcard/com.cz.babySister.activity
grep -ril "LoginActivity" *.dex
文章作者: J
文章链接: http://onejane.github.io/2021/04/21/违法应用移动TV取证分析/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 万物皆可逆向
支付宝打赏
微信打赏