【实战】某视频app的crack尝试

emmmm,是一个视频app,玩游戏的时候全局语音广告听到的,练练手。

app版本1.3.0

Redmi K30 5G(Android 10)

HttpCanary

jadx 1.1.0

目标一:破解登录加密方式

应该是检测了代理,使用代理则显示无网络链接。

我使用HttpCanary抓包,拿到登录明文:

主机Host:api.i69r7.com

POST /uc/app/v2/login HTTP/1.1
r: 4026847275
clientType: android
t: 1611839395333
channel: QD500
sign: 4cf2e5b8a57a60a07a9f99b78ec8d6de0cb122a6
X-JSL-API-AUTH: md5|1611841195|EsPrUIjgpp2Y|eccaa018eb2f0d3a3edd6b81d530b28f
deviceId: c5f0f2cc3b6f3a419b85452f32e15efc
deviceName: Redmi K30 5G
systemVersion: 10
version: 1.3.0
X-YD-Req-Token: 1611841195|FqOrf01aRQ8T|92fcef7544cef6f57edd2a1a644b4c2c
versionCode: 1300
Content-Type: application/json; charset=utf-8
Content-Length: 80
Host: api.i69r7.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/4.8.0

{"captcha":"","password":"ax9aKqMKRCKe6bzanQjNWA\u003d\u003d","username":"abaaba"}

通过jadx反编译dex部分看到登录时的POST Data设置逻辑:

image-20210209172656

用frida hook这个函数看看它传入了什么值

image-20210209172999

显然,第一个值是密码,第二个是MIYOU_APP_LOGIN_KEY字符串

secretEncrypt 是native 方法,定义在digest-lib库中,看起来像是自己写的库

emmmm,函数也不是动态注册的,所以一下就能找到函数:

image-20210209173036

一波分析发现,应该是简单的加密:

DES ECB模式 填充方式Pkcs7 无偏移量 密钥:MIYOU_APP_LOGIN_KEY

online tool:https://oktools.net/des

另外,实际上密钥用MIYOU_AP就可以了,DES的密钥长度只有64位,而且,虽然表面上是64位的,实际只有其中的56位被实际用于算法,其余8位可以被用于奇偶校验,并在算法中被丢弃。

但是这里很奇怪哦,native中的secretEncrypt方法居然调用了com/miu/lib_encipher/xcrypt/Encrypts中名为encryptDES,签名为(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;的java 方法。

我们模拟执行验证一下,看看它到底搞什么名堂

AndroidNativeEmu 模拟执行

AndroidNativeEmu项目介绍参考README,不赘述:https://github.com/AeonLucid/AndroidNativeEmu/

确实符合上面的分析。这波啊是java->native->java 我用我自己加密,加密逻辑还是在java中佛了。encryptDES方法的两个字符串参数就是明文和密钥。证据如下:

image-20210209173814

附代码:

import logging
import posixpath
import sys

from unicorn import UcError, UC_HOOK_MEM_UNMAPPED
from unicorn.arm_const import *

from androidemu.emulator import Emulator
from androidemu.java.java_class_def import JavaClassDef
from androidemu.java.java_method_def import java_method_def

from samples import debug_utils


# Create java class.
class DigestUtils(metaclass=JavaClassDef, jvm_name='com/miu/lib_encipher/jni/DigestUtils'):

    def __init__(self):
        pass

    @java_method_def(name='secretEncrypt', signature='(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;', native=True)
    def secretEncrypt(self, mu):
        pass

    def test(self):
        pass


class Encrypts(metaclass=JavaClassDef, jvm_name='com/miu/lib_encipher/xcrypt/Encrypts'):

    def __init__(self):
        pass

    @java_method_def(name='encryptDES', signature='(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;', native=False,  args_list=["jstring","jstring"])
    def encryptDES(self, *args, **kwargs):
        print("[+]encryptDES:",args[0].value, args[1].value)
        return "ch3nyeeeeeeeeeeeeee"

    def test(self):
        pass

# Configure logging
logging.basicConfig(
    stream=sys.stdout,
    level=logging.DEBUG,
    format="%(asctime)s %(levelname)7s %(name)34s | %(message)s"
)

logger = logging.getLogger(__name__)

# Initialize emulator
emulator = Emulator(
    vfp_inst_set=True,
    vfs_root=posixpath.join(posixpath.dirname(__file__), "vfs")
)

# emulator.mu.hook_add(UC_HOOK_CODE, debug_utils.hook_code)
emulator.mu.hook_add(UC_HOOK_MEM_UNMAPPED, debug_utils.hook_unmapped)

# Register Java class.
emulator.java_classloader.add_class(DigestUtils)
emulator.java_classloader.add_class(Encrypts)

# Load all libraries.
emulator.load_library("example_binaries/libm.so")
emulator.load_library("example_binaries/libc.so")
emulator.load_library("example_binaries/libstdc++.so")
lib_module = emulator.load_library("example_binaries/libdigest-lib.so")

# Show loaded modules.
logger.info("Loaded modules:")

for module in emulator.modules:
    logger.info("=> 0x%08x - %s" % (module.base, module.filename))


try:
    # Run JNI_OnLoad.
    # JNI_OnLoad will call 'RegisterNatives'.
    emulator.call_symbol(lib_module, 'JNI_OnLoad', emulator.java_vm.address_ptr, 0x00)

    # Do native stuff.
    digest_utils = DigestUtils()
    logger.info("Response from JNI call: %s" % digest_utils.secretEncrypt(emulator,"12345678","MIYOU_APP_LOGIN_KEY"))

    # Dump natives found.
    logger.info("Exited EMU.")
    logger.info("Native methods registered to DigestUtils:")

    for method in DigestUtils.jvm_methods.values():
        if method.native:
            logger.info("- [0x%08x] %s - %s" % (method.native_addr, method.name, method.signature))
except UcError as e:
    print("Exit at %x" % emulator.mu.reg_read(UC_ARM_REG_PC))
    raise

尝试破解VIP高清视频

这个app大概只提供两种清晰度,普通用户480P,VIP用户720P。

尝试从播放页com.erkhioqq.irivjiamm.avod.play.videodetail.MovieDetailActivity 入手看看能不能hook住app查看vip状态的的函数,直接返回当前用户是vip,但是实际上没找到。

抓取流量看到视频使用m3u8格式传输,其实抓到了链接:

https://video.gdyjdt.com/files/202012/20201207/ZWAV202012020528281/400kb/hls/index.m3u8

注意观察400kb是清晰度,对应480P。(其实一开始还没往这上面想,后来才发现,我就尝试了下:

import requests

for i in range(400,10000,100):
    url = "https://video.gdyjdt.com/files/202012/20201207/ZWAV202012020528281/{}kb/hls/index.m3u8".format(str(i))
    response = requests.head(url)
    if response.status_code != 404:
        print("[+]find:",url)
    else:
        print("[-]try {} kb fail".format(str(i)))

1000kb大概就对应VIP那个清晰度,但是也很垃圾,还是记下番号到正规网站找片把,1080p它不香吗。

APP中大量使用了StringFog加密字符串,写了个解密脚本方便分析:

DOWNLOADS_FILE_NAME = "irivjiamm"

import base64
import sys
def xor_func(str1,key):
    lkey=len(key)
    secret=[]
    num=0
    for each in str1:
        if num>=lkey:
            num=num%lkey

        secret.append( chr( each^ord(key[num]) ) )
        num+=1
    return "".join(secret)



def decrypt(s):
    return xor_func(base64.b64decode(s), DOWNLOADS_FILE_NAME)


def encrypt(s):
    str = xor_func(bytes(s,encoding='utf-8'), DOWNLOADS_FILE_NAME).encode('utf-8')
    return base64.b64encode(str)



if __name__ == '__main__':
    tips = "need 2 parameter: d/e cipher/plain"
    args = []
    if __file__ in sys.argv[0]:
        args = sys.argv[1:]
    else:
        args = sys.argv
    if len(args) != 2:
        print(tips)
        exit(-1)

    if args[0] == 'd':
        print(decrypt(args[1]))
    elif args[0] == 'e':
        print(encrypt(args[1]))

后续还可以看几个函数,看看能不能再整点活

MoiveDetailActivity.showMovieCache

MoiveDetailActivity.download

MoiveDetailActivity.handleDownload

附件:

base.apk

备用蓝奏云:https://wwe.lanzous.com/iWubulikvuf