2025数证杯移动终端及apk部分复盘

Apk

第一题

将检材直接拖入Jadx即可获得 包名为:net.net

第二题

根据Jadx分析可以看出该Apk是有壳的,加固厂商为360 具体表现形式为libjiagu.so,这里我们使用BlackDex进行脱壳即可

将脱壳得到的dex文件拖入Jadx并按题意搜索System. 并且符合获取系统时间戳的方法为 currentTimeMillis

第三题

这里可以直接搜索getVer()查询方法签名和完整路径,再通过hook的形式获取到方法的返回值即可 返回值为:

脚本核心代码如下:

第四题

题意说需要打开第二个页面,直接分析SecondActivity,安装X查询引用这个活动的位置,分析可以得出拼接合成链接的方法如下

可以看出,主要就是获取了很多的信息如 MainActivity.apkHash、MainActivity.getVer()、MainActivity.f417ts),同样的编写hook脚本直接获取返回值再拼接即可,跳转到第二个页面的链接如下:http://localhost:60723/K0pQvBZ38ykL26OzfshqYTHC2f7RUJYIgrmIF6GcORU%3D?hash=983f8a605d16530190c09b0884d7cef1&ver=4.56.23&ts=1727899789

脚本核心代码如下:

第五题

因为已经告诉你第二页面的8为授权码,那么去到SecondActivity分析代码即可,分析结果得出该验证授权码的方法没有传参的部分,方法如下

我们直接编写一个脚本进行爆破即可,可以得出第二页面授权码为:

脚本如下所示

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import itertools
import time
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed

def java_string_hashcode(s):
    """
    计算Java String的hashCode
    算法: s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
    """
    h = 0
    for char in s:
        h = (31 * h + ord(char)) & 0xFFFFFFFF
    # 转换为有符号32位整数
    return h - 0x100000000 if h > 0x7FFFFFFF else h

def test_string(s, target_hash):
    """测试单个字符串是否匹配目标哈希值"""
    if java_string_hashcode(s) == target_hash:
        return s
    return None

def brute_force_numeric(length_range, target_hash):
    """暴力破解数字组合"""
    print(f"[*] 开始暴力破解 {length_range[0]}-{length_range[1]} 位数字...")
    
    found = None
    for length in range(length_range[0], length_range[1] + 1):
        print(f"   正在尝试 {length} 位数字...")
        
        # 生成所有可能的数字组合
        for combo in itertools.product('0123456789', repeat=length):
            test_str = ''.join(combo)
            if java_string_hashcode(test_str) == target_hash:
                found = test_str
                break
        
        if found:
            break
    
    return found

def brute_force_alphanumeric(length_range, target_hash, charset=None):
    """暴力破解字母数字组合"""
    if charset is None:
        charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    
    print(f"[*] 开始暴力破解 {length_range[0]}-{length_range[1]} 位字母数字...")
    
    found = None
    for length in range(length_range[0], length_range[1] + 1):
        print(f"   正在尝试 {length} 位字符...")
        
        for combo in itertools.product(charset, repeat=length):
            test_str = ''.join(combo)
            if java_string_hashcode(test_str) == target_hash:
                found = test_str
                break
        
        if found:
            break
    
    return found

def smart_brute_force(target_hash):
    """智能暴力破解,优先尝试常见模式"""
    print("=" * 60)
    print("🎯 开始智能暴力破解授权码")
    print(f"🎯 目标哈希值: {target_hash}")
    print("=" * 60)
    
    start_time = time.time()
    
    # 常见测试用例
    common_tests = [
        # 特殊数字
        "1919810", "114514", "123456", "654321", "888888", "999999",
        "000000", "111111", "222222", "333333", "444444", "555555",
        "666666", "777777", 
        
        # 与哈希值相关的数字
        "711638", "638849", "711638849", "191914", "191914514",
        
        # 日期格式
        "202411", "202412", "202413", "202414", "202415",
        "110111", "110112", "110113", "110114", "110115",
        "240101", "240102", "240103", "240104", "240105",
        
        # 手机号格式
        "138001", "139001", "158000", "186000", "189000",
        
        # 其他常见数字
        "100000", "200000", "300000", "400000", "500000",
        "600000", "700000", "800000", "900000", "100001"
    ]
    
    print("[1] 测试常见数字模式...")
    for test_str in common_tests:
        if java_string_hashcode(test_str) == target_hash:
            elapsed = time.time() - start_time
            print(f"✅ 找到授权码: {test_str} (耗时: {elapsed:.2f}秒)")
            return test_str
    
    # 多线程暴力破解
    print("[2] 开始多线程暴力破解...")
    
    # 定义搜索范围
    search_ranges = [
        (4, 4),   # 4位数字
        (5, 5),   # 5位数字  
        (6, 6),   # 6位数字
        (7, 7),   # 7位数字
        (8, 8),   # 8位数字
    ]
    
    with ThreadPoolExecutor(max_workers=4) as executor:
        # 提交任务
        future_to_range = {
            executor.submit(brute_force_numeric, length_range, target_hash): length_range 
            for length_range in search_ranges
        }
        
        # 收集结果
        for future in as_completed(future_to_range):
            result = future.result()
            if result:
                elapsed = time.time() - start_time
                print(f"✅ 找到授权码: {result} (耗时: {elapsed:.2f}秒)")
                executor.shutdown(wait=False)
                return result
    
    # 如果数字没找到,尝试字母数字组合
    print("[3] 尝试字母数字组合...")
    result = brute_force_alphanumeric((4, 6), target_hash, '0123456789abcdef')
    if result:
        elapsed = time.time() - start_time
        print(f"✅ 找到授权码: {result} (耗时: {elapsed:.2f}秒)")
        return result
    
    elapsed = time.time() - start_time
    print(f"❌ 未找到匹配的授权码 (总耗时: {elapsed:.2f}秒)")
    return None

def test_specific_cases(target_hash):
    """测试特定的可疑字符串"""
    print("[*] 测试特定可疑字符串...")
    
    suspicious_strings = [
        # 基于类名和方法的猜测
        "netnet", "Second", "Activity", "auth", "code", 
        "AuthCode", "NetNet", "SECOND", "ACTIVITY",
        "netnetSecond", "SecondActivity", "netnetSecondActivity",
        
        # 组合
        "netnet123", "Second123", "Activity123", 
        "auth123", "code123", "AuthCode123",
        
        # 其他常见授权码
        "admin", "password", "secret", "key", "pass",
        "Admin", "Password", "Secret", "Key", "Pass",
    ]
    
    for test_str in suspicious_strings:
        hash_val = java_string_hashcode(test_str)
        if hash_val == target_hash:
            print(f"✅ 找到授权码: {test_str}")
            return test_str
        else:
            print(f"   测试: '{test_str}' -> {hash_val}")
    
    return None

if __name__ == "__main__":
    target_hash = -711638849
    
    print("Java String hashCode 暴力破解工具")
    print("目标哈希值:", target_hash)
    print()
    
    # 首先测试特定可疑字符串
    result = test_specific_cases(target_hash)
    if result:
        print(f"\n🎉 最终授权码: {result}")
        exit(0)
    
    print("\n" + "="*50)
    print("开始暴力破解...")
    print("="*50 + "\n")
    
    # 进行智能暴力破解
    auth_code = smart_brute_force(target_hash)
    
    if auth_code:
        print(f"\n🎉 破解成功!授权码为: {auth_code}")
        print(f"🔍 验证: java_string_hashcode('{auth_code}') = {java_string_hashcode(auth_code)}")
    else:
        print("\n❌ 破解失败,未找到匹配的授权码")
        print("💡 建议:")
        print("   1. 检查哈希值是否正确")
        print("   2. 尝试更大的搜索范围")
        print("   3. 检查授权码是否包含特殊字符")

移动终端(能用取证软件分析出的不写过程)

第一题

8390

第二题

这里如果取证软件分析不出结果可以到/system/ 目录下查询package.xml文件,该文件记录了初次安装日期和上次更新日期,通过包名搜索即可it=install time ut=updatetime 这里的值是16进制编码的unix时间戳 使用在线工具解一下即可 时间为09月24日

第三题

3FE61F

第四题

直接进图库找出来算就行 85A51D

第五题

宽高被修改了 编写脚本进行CRC爆破得出正确宽高为1809x222 得到答案为boost

import struct
import zlib
from tqdm import tqdm  # 需要安装: pip install tqdm

# 读取PNG文件
with open('imtoken助记词1.jpg', 'rb') as f:
    data = f.read()

# 找到IHDR块的位置
ihdr_start = 8
ihdr_data = data[ihdr_start+8:ihdr_start+8+13]
original_crc = struct.unpack('>I', data[ihdr_start+8+13:ihdr_start+8+13+4])[0]

width_orig = struct.unpack('>I', ihdr_data[0:4])[0]
height_orig = struct.unpack('>I', ihdr_data[4:8])[0]
print(f"当前文件中的宽高: {width_orig} x {height_orig}")

# 基于常见图片比例猜测可能的高度
common_heights = [400, 500, 600, 720, 768, 800, 900, 1024, 1080, 1200, 1440]

print("开始智能爆破...")

# 优先尝试常见的高度值
for height in tqdm(common_heights, desc="测试常见高度"):
    for width in range(100, 4000, 1):  # 宽度从100到4000
        ihdr_block = b'IHDR' + struct.pack('>II', width, height) + ihdr_data[8:13]
        crc32 = zlib.crc32(ihdr_block)
        if crc32 == original_crc:
            print(f"\n找到正确尺寸: {width} x {height}")
            # 修复文件
            new_ihdr_data = struct.pack('>II', width, height) + ihdr_data[8:13]
            new_data = data[:ihdr_start+8] + new_ihdr_data + data[ihdr_start+8+13:]
            
            with open('fixed.png', 'wb') as f:
                f.write(new_data)
            print("文件已修复并保存为 fixed.png")
            exit(0)

print("常见高度未匹配,开始全面搜索...")

# 全面搜索
for height in tqdm(range(1, 2001), desc="全面搜索"):
    for width in range(1, 4001):
        ihdr_block = b'IHDR' + struct.pack('>II', width, height) + ihdr_data[8:13]
        crc32 = zlib.crc32(ihdr_block)
        if crc32 == original_crc:
            print(f"\n找到正确尺寸: {width} x {height}")
            # 修复文件
            new_ihdr_data = struct.pack('>II', width, height) + ihdr_data[8:13]
            new_data = data[:ihdr_start+8] + new_ihdr_data + data[ihdr_start+8+13:]
            
            with open('fixed.png', 'wb') as f:
                f.write(new_data)
            print("文件已修复并保存为 fixed.png")
            exit(0)

print("未找到匹配的宽高。")
第六题

全局搜索助记词就行 答案为:delay

第七题

这里的“助记词3”指的是文本内容,可以文件为“备忘录”,因为和助记词图片在相同目录下,使用7z打开发现这是一个word文档,添加后缀后发现里面有“助记词3”的文本但是没有英文,将文件拖入010可以发现在结尾处有四个助记词单词 答案为:quarter

第八题

因为知道12个助记词以及地址,可以自己手动编写脚本去爆破出正确的助记词顺序,需要注意的是BIP39协议,可以大幅缩短爆破所需时间,答案为:segment

第九题

这题要是软件分析不出来那就不用搞了,要是有知道怎么搞得同学欢迎加联系方式探讨

第十题

这部分有几个可疑的app 不知道具体作用的百度一下即可,可以得出使用的app为叮通 进入文件目录导出后使用jadx分析即可,当然文件夹的名字就是包名,不放心可以导出验证,包名为:dingtong.saichuang

第十一题

首先要知道密码我们得先拿到数据库方面之后做验证,文件目录中查询到该软件运行产生得数据库名字为dingtong.db 之后导出Apk对该数据库得加密方式进行分析,因为android基本都是使用sqllite数据库,所以可以直接搜索sqlcliper进行查找,或者直接使用数据库名进行搜索,对代码分析后可以得出密码为:@1@#!aajsk1*JKJ

第十二题

根据打开的数据库我们可以得出有提到过密码 对该密码进行解密后可以得到密码为:QWERT666

第十三题

这里需要去查询“密信”app的数据库,在数据库内可以找到密码 使用密码可以解密出身份证照片,得到门牌号为722

其实这部分我也是因为数据库不小心翻到的,在做和app相关的题目的时候,数据库是一个非常重要的东西,如果时间允许可以点开看看

第十四题

同样的,密码同样保存在数据库中,找到密码之后只需要全局搜索该文件,导出即可 答案为:935629

第十五题

这个题当时时间来不及了,大概知道需要导出密信这个app安装运行


2025数证杯移动终端及apk部分复盘
http://lhyki.cn/archives/2025ShuZhengCup
发布于
2025年11月11日
许可协议