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安装运行