APK取证
1.分析方俊朗phone.E01检材,筛选优质客户应用将用户查询记录存储在一个加密的本地数据库中。请问该加密数据库的文件名是什么?[答案格式:12_abc.db]
题目说了这边是筛选优质客户,其实和手机取证最后一题一样的,先打开方俊朗的app列表看看

一看就是这一个软件,我们可以看到包名为:com.example.predictor
根据这个包名我们去/data/data目录下进行搜索即可

在/data/data下直接根据包名即可搜索到文件信息,而在/data/app下根据包名搜索即可得到对应apk,这对做APK取证很重要
我们到这个目录之下,题目问加密的本地数据库,那肯定是去databases里边进行查找了

定位到数据库,就一个数据库文件
点一下发现确实加密的
所以文件名就是chat_history.db
当然也可以去apk里边查找

只是不太好找,更适合作为验证项验证答案
2.分析方俊朗phone.E01检材,该应用使用了哪种密钥派生算法来生成数据库加密密钥?请写出完整的算法标识名称。[答案格式:DFBDSDFGG123]
问我们这个密钥的派生算法了,那自然需要来看看配置文件并分析一下这一个apk文件了
先看看配置文件

可以看到说是PBKDF2加密,但这边只给了一个密钥版本,不是密钥派生方法,所以我们还是应该去apk查看
像之前说的,我们可以在/data/app下根据包名搜索即可得到对应apk

提取出来放到雷电看看


安装大概界面长这样子,这边可以进行仿真查看,发现有三条记录
但是这三条记录意义不大,因此这边我就不介绍如何仿真的手机apk了,后边有需要再讲,我们还是进jadx看看
发现被加壳了

雷电进行脱壳后打开,真是层层加壳啊

搜索PBKDF2后,我们在0x76388f130000_0x658c.dex找到了关键KeyDeriver

在这边我们看到了整个主密钥的派生方法,获取三个盐值,组合种子,然后创建PBKDF2密钥
当然对于本题来说核心的还是这一句
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
这是获取PBKDF2密钥工厂的实例,所以完整的算法标识名称是PBKDF2WithHmacSHA256
3.分析方俊朗phone.E01检材,该应用的密钥派生过程中使用了多少次迭代运算?[答案格式:345678]

在上一题的配置文件/data/data/com.example.predictor/shared_prefs/app_config.xml可以看到这边写的很明确是进行了10000次迭代

当然apk里也自然有对应的记录存在
4.分析方俊朗phone.E01检材,该应用检测动态调试工具时探测了哪个本地端口号?[答案格式:64321]
后边的apk分析全部需要先脱壳再说
这边既然说是本地端口号,先搜一下127.0.0.1看看有没有收获

过滤出来两个端口

发现上边那条记录是Utils,是 ConstraintLayout 库内部的调试基础设施,帮助开发者理解和优化布局性能的
所以很大概率是下边那一个

这一个是SecurityManager的类,在反调试反逆向做了关键,很符合题目所说的检测动态调试工具,看上述代码可以看出这就是一个为检测frida注入痕迹的而开的端口,符合题意
所以本题答案即为27042
5.分析方俊朗phone.E01检材,该应用密钥由多个"盐值片段"拼接后派生而来。请问第一个盐值片段的具体内容是什么?[答案格式:SAltsaltsalt]

我们在KeyDeriver已经看到了应用密钥是这样子多个盐值片段拼接后派生而来
问第一个盐值片段,写的很清楚

就在下边,所以答案是Pr3d1ct0r
6.分析方俊朗phone.E01检材,当密钥派生过程出现异常时,应用会使用一个硬编码的备用密钥。请问该备用密钥的完整内容是什么?[答案格式:salt_dlefe_123_dfefaf]

跟上边几题一样,就在密钥派生这部分写了异常的备用密钥,明文写在这边
f4ll8ack_k3y_2024_pr3d1ct0r
7.分析方俊朗phone.E01检材,分析"优质客户预测"应用,该应用的安全检测模块通过检查一个特定的类名来判断设备是否安装了Hook框架。请问被检测的完整类名是什么?[答案格式:ru.foefn.DFeoagn.dfoandf.xoggdg]
题目已经说是和Hook框架相关了
因此我们可以对这些Hook框架相关的关键词进行搜索过滤
Xposed
Class.forName
Hook

发现Xposed存在线索,这是Android平台上最著名的Hook框架之一

步入后马上就能看见安全检测模块正是通过加载类de.robv.android.xposed.XposedBridge来检测是否设备安装了Xposed框架这种Hook框架
其实就在第四题的SecurityManager下边,毕竟都是作为安全检测,都在一起
所以这一题的答案是de.robv.android.xposed.XposedBridge
8.分析方俊朗phone.E01检材,该应用在偏好设置文件中存储了一个密钥校验值。请问存储该校验值的键名(key)是什么?[答案格式:ab_dfefegad_cadfeg]

我们在一开始的配置文件这边可以看到类似字段
后边跟着一串值,key的名字是db_integrity_check,但不能确定这就是密钥检验值
我们得在jadx里边确认一下

搜索后可以在KeyDeriver找到它
可以看到这边是获取了MD5算法实例后,与key一起计算MD5散列值
"db_integrity_check", checksum
明显是把密钥检验值checksum放到了key:db_integrity_check中,所以本题答案是db_integrity_check
9.分析方俊朗phone.E01检材,该应用加密数据库中存储对话记录的数据表名是什么?[答案格式:liaotian_dfelge]

题目已经指明了本题的目标是这个应用的加密数据库
我们很容易就能在data路径下找到这个软件的/databases/chat_history.db
但是不知道密码一下子打不开,我们去jadx看看能不能找到相关线索


发现确实是这边把生成的聊天记录加密存到了chat_records这个表,所以答案即为这个
当然了,这数据库密码只是难搞,但并非不能得到
毕竟我们一早在apk第二题就找到了相关的配置文件,在上文其实也定位到了密钥派生逻辑KeyDeriver

PBEKeySpec spec = new PBEKeySpec(combinedSeed.toCharArray(), salt1.getBytes(), PBKDF2_ITERATIONS, 256);
上文也分析过了,密钥是使用的PBKDF2WithHmacSHA256,迭代了10000次,最后的密钥长度为256bit,即32字节,完整流程是这样子
String combinedSeed = signatureHash + salt1 + salt2 + salt3;
主口令 = 签名证书SHA256 + 第一个盐值片段 + 第二个盐值片段 + 第三个盐值片段
签名证书就是签名证书啊

雷电分析里边直接就有其实4f030268de5e58c26bb6eec54b1be7c0330dde2bf857ee2b3b26c50b25deb239
接下来是三段盐

明显第一段盐是Pr3d1ct0r,第三段是X9kZ!qW3
第二段是三段字符串拼起来,即"v2.0_" + net.sqlcipher.BuildConfig.BUILD_TYPE + "_S@lt"
而中间这个双击就能跳转过去看值

所以第二段盐是 v2.0_release_S@lt
所以主口令是
4e1544532b5c88f9acec7a2f5ba79d09bb39a4709b9a9ec36e18c22f540f3270Pr3d1ct0rv2.0_release_S@ltX9kZ!qW3
即可计算主密钥
import hashlib
signature_hash = "4f030268de5e58c26bb6eec54b1be7c0330dde2bf857ee2b3b26c50b25deb239"
salt1 = "Pr3d1ct0r"
salt2 = "v2.0_release_S@lt"
salt3 = "X9kZ!qW3"
combined_seed = signature_hash + salt1 + salt2 + salt3
key = hashlib.pbkdf2_hmac(
"sha256",
combined_seed.encode("utf-8"),
salt1.encode("utf-8"),
10000,
dklen=32
).hex()
print("combined_seed =", combined_seed)
print("db_key =", key)
print("md5(db_key) =", hashlib.md5(key.encode("utf-8")).hexdigest())

得到密钥与md5值,与配置文件中的md5检验值一致,证明了密钥正确
93b16df295656676dd760e6dcfd71623e446b51c91b028ac84c88958fc3ad98b

用这个软件就能打开加密的数据库文件了

打开就能发现表名确实是这一个chat_records
不过这边我不知道为什么可视化的几个解密软件都不太对劲,写了密码解不开,但命令行的就能用同一个密码却能成功解密
import hashlib
import sqlite3
from pathlib import Path
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
PASSPHRASE = "93b16df295656676dd760e6dcfd71623e446b51c91b028ac84c88958fc3ad98b"
def aes_cbc_decrypt(key, iv, ciphertext):
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
decryptor = cipher.decryptor()
return decryptor.update(ciphertext) + decryptor.finalize()
def decrypt_sqlcipher4_db():
input_db = Path("chat_history.db")
output_db = Path("chat_history_plain.db")
if not input_db.exists():
print("[-] 未找到 chat_history.db")
return
data = input_db.read_bytes()
page_size = 4096
reserve_size = 80
usable_size = page_size - reserve_size
if len(data) % page_size != 0:
raise RuntimeError("数据库大小不是 4096 的整数倍")
page_count = len(data) // page_size
print(f"[+] 数据库页数: {page_count}")
file_salt = data[:16]
print("[+] SQLCipher 文件 salt:", file_salt.hex())
aes_key = hashlib.pbkdf2_hmac(
"sha512",
PASSPHRASE.encode("utf-8"),
file_salt,
256000,
dklen=32,
)
print("[+] SQLCipher AES key:", aes_key.hex())
out = bytearray()
for page_no in range(1, page_count + 1):
start = (page_no - 1) * page_size
end = page_no * page_size
page = data[start:end]
iv = page[usable_size:usable_size + 16]
if page_no == 1:
ciphertext = page[16:usable_size]
plaintext_part = aes_cbc_decrypt(aes_key, iv, ciphertext)
out += b"SQLite format 3\x00"
out += plaintext_part
out += b"\x00" * reserve_size
else:
ciphertext = page[:usable_size]
plaintext_page = aes_cbc_decrypt(aes_key, iv, ciphertext)
out += plaintext_page
out += b"\x00" * reserve_size
output_db.write_bytes(out)
print(f"[+] 已生成明文数据库: {output_db}")
return output_db
def verify_plain_db(output_db):
print("[+] 验证明文数据库...")
conn = sqlite3.connect(output_db)
cur = conn.cursor()
cur.execute("SELECT name FROM sqlite_master WHERE type='table';")
tables = [row[0] for row in cur.fetchall()]
print("[+] 表名:")
for table in tables:
print(" ", table)
if "chat_records" in tables:
print("\n[+] chat_records 前5条记录:")
cur.execute("SELECT * FROM chat_records LIMIT 5;")
for row in cur.fetchall():
print(" ", row)
conn.close()
print("[+] 验证完成")
def main():
try:
output_db = decrypt_sqlcipher4_db()
verify_plain_db(output_db)
except Exception as e:
print(f"[-] 解密失败: {e}")
if __name__ == "__main__":
main()
所以附一个解密代码

10.分析方俊朗phone.E01检材,该应用的密钥生成逻辑,该应用中的第三个盐值片段是通过逐字符拼接生成的。请问该片段拼接后的完整内容是什么?[答案格式:K8m!pQ2x]
上边已经分析过了
密钥生成逻辑在KeyDeriver,密钥是三部分盐值
第三部分就在这边

所以拼起来就是X9kZ!qW3,就好了
11.分析方俊朗phone.E01检材,该应用使用了载荷在内存中直接加载而不在磁盘落地。若选手希望通过Frida动态拦截明文的DEX字节数组,应该Hook该应用壳的哪个私有方法?[答案格式:loadDexFromMemory]
想要拦截明文的DEX字节数组,肯定需要先搞明白这个壳对DEX字节数组的核心加载流程

我们对原来未脱壳的进行解包(把后缀从apk改成zip即可),可以看到在assets路径下存在不寻常的payload.dat,怀疑和壳加载过程有关,搜索一下


我们定位到了壳代码所在地

可以看到很明确的Android应用保护方案,大致流程如下
attachBaseContext()
-> readAsset("payload.dat") //先读加密的Dex文件
-> decrypt() //之后解密
-> parseDexPayload() //解密payload内容
-> initDexFromMemory() //把解密的存到内存里
-> InMemoryDexClassLoader //合并到应用主ClassLoader

所以可以看到,应用将真实 DEX 解密到内存后,是调用的私有方法initDexFromMemory
因此,如果使用 Frida 动态拦截明文 DEX 字节数组,也应该Hook:initDexFromMemory才对
答案即为initDexFromMemory,其参数就是解密后的明文DEX字节数组
12.分析方俊朗phone.E01检材,分析应用的壳代码逻辑,其解密密钥由3个字符串片段混淆拼接而成。请通过静态分析,还原用于解密的3个片段拼接合并后的完整密钥明文。[答案格式:Ab6de_a8bc4d_a5b_345d]
我们在上一题就找到了壳代码所在地ShellApplication
在壳代码中可以看到:

即我们本题所需要的解密密钥三部分
一个个解就好了
首先是getKeyPart1(),明显解base64:U2gzbGxf

得到第一段:Sh3ll_
继续分析第二段:

逐个与17异或即可
xor_bytes = [93, 33, 112, 117, 34, 99, 78, 90]
result = ''.join(chr(b ^ 17) for b in xor_bytes)
print(result)
得到第二段:L0ad3r_K

最后第三段只是反转而已:3y_2024!
拼起来,得到最后答案是Sh3ll_L0ad3r_K3y_2024!
13.接上题,当选手尝试使用frida-dexdump等通用脱壳工具动态附加时,应用会立刻闪退。请分析该逻辑,写出该线程触发进程自杀所调用的完整Java系统方法签名。[答案格式:abcd.abcd.Abcdabc.abcd]
题目说了,是使用dexdump等脱壳的时候会闪退的
就在同一个地方我们可以看见这样子一个反dump方法

可以看到调用的是lambdastartAntiDumperDaemon0方法

明显看见了最后自杀是System.exit(0)来进行的进程自杀
题目说了写完整Java系统方法签名,所以答案是java.lang.System.exit(0);
14.分析方俊朗phone.E01检材,方俊朗使用其内部通联工具时,共加入过几个群?[答案格式:7]
终于是跳出刚刚那一个预测优质客户软件了,步入了下一个软件,内部通联工具
从各个角度,不管是看所有人都有这个软件也好,排除法也好,我们都很容易确定

内部通联工具就是这一个com.socialchat.social_chat_app
那这一题问的很明确,加入过几个组,很明显是要找这个软件的数据库,依旧是同样的手法,我们去/data/data/包名/databases来找

即可看见聊天记录的数据库,但是加密了

在配置文件这边找到了对应的db_password
<string name="flutter.db_password">Pgs-dbw1776826167125Good</string>
但是用这个password却无法解开这个db
我们去看看apk的具体逻辑
因为这个APP是Flutter应用,所以真正的逻辑不在Java层,在native文件才是

解压apk,找到它,我们这边可以静态来逆向还原一些逻辑,还看见了应用自带有SQLCipher库
package:social_chat_app/data/database/database_helper.dart
在字符串中可以看到这样子的一句,还能看到截取之类的字符串,但是并不清晰,不明白是如何进行的
还是得进行Frida的动态hook才行(我不太会hook。。只能ai写一个脚本了)
(其实Flutter AOT反编译也可以)
function waitForAppContext(callback) {
let count = 0;
const timer = setInterval(function () {
Java.perform(function () {
try {
const ActivityThread = Java.use("android.app.ActivityThread");
const app = ActivityThread.currentApplication();
if (app !== null) {
clearInterval(timer);
const ctx = app.getApplicationContext();
console.log("[+] getApplicationContext ok");
callback(ctx);
return;
}
count++;
console.log("[*] waiting for Application context... " + count);
} catch (e) {
count++;
console.log("[*] waiting context error:", e);
}
if (count >= 30) {
clearInterval(timer);
console.log("[-] still cannot get Application context");
}
});
}, 500);
}
setImmediate(function () {
console.log("[*] no-login social_chat.db decrypt hook loaded");
Java.perform(function () {
waitForAppContext(function (ctx) {
try {
const prefs = ctx.getSharedPreferences("FlutterSharedPreferences", 0);
let raw = prefs.getString("flutter.db_password", null);
console.log("[+] flutter.db_password =", raw);
if (raw === null) {
console.log("[-] 没读到 flutter.db_password");
console.log("[-] 确认 XML 是否在 /data/user/0/com.socialchat.social_chat_app/shared_prefs/FlutterSharedPreferences.xml");
return;
}
const realPassword = raw.substring(2, raw.length - 1);
console.log("[+] raw password =", raw);
console.log("[+] real password =", realPassword);
const dbPath = "/data/user/0/com.socialchat.social_chat_app/databases/social_chat.db";
console.log("[+] db path =", dbPath);
forceLoadSqlcipher();
setTimeout(function () {
waitForSqlcipherAndRun(dbPath, realPassword);
}, 1000);
} catch (e) {
console.log("[-] main logic error:", e);
}
});
});
});

成功发现真实密码,大致就是raw.substring(2, raw.length -1)
所以密码是s-dbw1778570149492Goo

解开后查看conversation即可发现加了技术部和话务组
所以答案是2,加了这俩群
15.分析方俊朗phone.E01检材,方俊朗通过物联网设备漏洞,共获得多少用户信息?[答案格式:10]
上一题我们已经解开了数据库
这边一开始的思路是通过user里边的password_hash和password_salt进行爆破
但显然不太能爆破出来
因此我们这边不能仿真,但是我们可以找到message加密逻辑,可以直接解密其发送过的信息
从user.config_data可以读到enc_key和enc_iv
{"enc_key": "bGx3iDfEn4O5kW+2hf0E594C+jcyE1aujg0RIE5DPMM=", "enc_iv": "y1q+/i9a/85OsPry0WMffw=="}
import base64
enc_key = "bGx3iDfEn4O5kW+2hf0E594C+jcyE1aujg0RIE5DPMM="
enc_iv = "y1q+/i9a/85OsPry0WMffw=="
key = base64.b64decode(enc_key)
iv = base64.b64decode(enc_iv)
print(len(key), key.hex())
print(len(iv), iv.hex())
输出发现key是256bit的,iv长16字节
所以猜测是AES-256-CBC解密,当然最关键的判断标准就是尝试解密发现成功了,都成功了肯定是真的啊
写脚本
import argparse
import base64
import csv
import json
import sqlite3
from pathlib import Path
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
def pkcs7_unpad(data: bytes) -> bytes:
if not data:
return data
pad = data[-1]
if 1 <= pad <= 16 and data[-pad:] == bytes([pad]) * pad:
return data[:-pad]
return data
def aes_cbc_decrypt_base64(cipher_b64: str, key: bytes, iv: bytes) -> str:
cipher_data = base64.b64decode(cipher_b64)
decryptor = Cipher(
algorithms.AES(key),
modes.CBC(iv)
).decryptor()
plain = decryptor.update(cipher_data) + decryptor.finalize()
plain = pkcs7_unpad(plain)
return plain.decode("utf-8", errors="replace")
def get_key_iv(conn: sqlite3.Connection, user_id: str | None):
conn.row_factory = sqlite3.Row
cur = conn.cursor()
if user_id:
row = cur.execute(
"SELECT id, config_data FROM user WHERE id = ?",
(user_id,)
).fetchone()
else:
row = cur.execute(
"SELECT id, config_data FROM user LIMIT 1"
).fetchone()
if not row:
raise RuntimeError("user 表中没有找到用户记录")
config_data = row["config_data"]
if not config_data:
raise RuntimeError("user.config_data 为空")
config = json.loads(config_data)
key = base64.b64decode(config["enc_key"])
iv = base64.b64decode(config["enc_iv"])
print("[+] use user_id:", row["id"])
print("[+] AES key:", key.hex())
print("[+] AES iv :", iv.hex())
return key, iv
def convert_messages(input_db: Path, output_csv: Path, user_id: str | None):
conn = sqlite3.connect(input_db)
conn.row_factory = sqlite3.Row
key, iv = get_key_iv(conn, user_id)
cur = conn.cursor()
rows = cur.execute(
"""
SELECT *
FROM message
ORDER BY create_at ASC
"""
).fetchall()
if not rows:
print("[-] message 表为空")
conn.close()
return
fieldnames = list(rows[0].keys())
# 增加一列明文
if "content_plain" not in fieldnames:
fieldnames.append("content_plain")
output_rows = []
for row in rows:
item = dict(row)
cipher_content = row["content"]
try:
plain = aes_cbc_decrypt_base64(cipher_content, key, iv)
except Exception as e:
plain = f"[DECRYPT_ERROR] {e}"
item["content_plain"] = plain
output_rows.append(item)
with output_csv.open("w", encoding="utf-8-sig", newline="") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(output_rows)
conn.close()
print("[+] message 总数:", len(output_rows))
print("[+] 已导出:", output_csv)
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"-i",
"--input",
default="social_chat_plain.db",
help="输入:已经解密后的普通 SQLite 数据库"
)
parser.add_argument(
"-o",
"--output",
default="messages_plain.csv",
help="输出 CSV"
)
parser.add_argument(
"-u",
"--user-id",
default=None,
help="指定 user.id,不填则默认取 user 表第一条"
)
args = parser.parse_args()
input_db = Path(args.input)
output_csv = Path(args.output)
if not input_db.exists():
raise FileNotFoundError(f"找不到数据库文件: {input_db}")
convert_messages(input_db, output_csv, args.user_id)
if __name__ == "__main__":
main()

得到利用物联网获取了32户
16.分析周文杰Image.zip检材,周文杰跟其犯罪团伙人员内部通联工具包名是?[答案格式:com.apt.app]
跟上边方俊朗的不能说大差不差吧,只能说一模一样
就是这一个apk

com.socialchat.social_chat_app
填写包名即可
17.分析周文杰Image.zip检材,内部通联app聊天数据库名称是?[答案格式:abc.db]

在data文件夹一路找即可,还是跟刚刚一样
就是这一个social_chat.db
18.分析周文杰Image.zip检材,内部通联app聊天数据库密码保存在哪个文件中?[答案格式:Abc.txt]
依旧是差不多一样的,根据应用数据目录中的偏好配置文件进行分析:
/shared_prefs/FlutterSharedPreferences.xml
在 Flutter 应用中,常见敏感配置会保存在该文件中

所以密码就存在 FlutterSharedPreferences.xml文件中
19.分析周文杰Image.zip检材,周文杰内部通联app聊天数据库密码是?[答案格式:123-abc]

就是18那个文件,打开即可看见Pgs-dbw1776853545473Good
但是我们刚刚在14题已经明确了密码应该是s-dbw1776853545473Goo才对,俩软件hash一样,逻辑肯定也一样
实在不放心,完全可以拿这个解密试试看,能解就是密码

成功,所以本题答案的密码就是s-dbw1776853545473Goo
20.分析周文杰Image.zip检材,内部通联app聊天数据使用的什么加密算法?[答案格式:ABCDEF]

也和15一样,因为apk都是同一个,加密手法也一样,都是AES-256-CBC,我看答案写AES好像也算对了
实在不放心可以用15的同款脚本尝试解密,解密数据库也可以拿之前第九题的脚本跑

能解密出一套完整的可读文字,自然是正确的,说明和之前的一样,的确是AES-256-CBC
21.分析周文杰Image.zip检材,内部通联app用户密码的盐值是?[答案格式:1234abcd]
上两题已经说明了密码s-dbw1776853545473Goo
解密db文件

a3f8d9c2e1b4h7g6k9m2n5p8q1r4t7w得到密码盐
22.分析周文杰Image.zip检材,记录周文杰内部通联app登录密码提示的应用包名是?[答案格式:com.temp.app]
根据题目意思,很明显,存在有一个app,里边记录了周文杰内部通联app登录密码的提示
我们在 /data/data/ 目录中排查其他可疑 App,发现两个记事本类应用:

com.wenying.notebook
com.jinghong.notebookkssjh
一个个看就好了
在com.jinghong.notebookkssjh发现了记事本数据库NotePal.db

提取一下

preview_content = 英文2 +数字6
所以该笔记明显是登录密码提示,因此记录周文杰内部通联 App 登录密码提示的应用包名为:com.jinghong.notebookkssjh
23.分析周文杰Image.zip检材,内部通联app登录密码是?[答案格式:123abc]
上一题已经说了

是英文2+数字6
但是不知道是什么,只能进行爆破,好在我们21已经知道了用户名是zhouwenjie,还知道了password的密码和salt

利用hashcat爆破即可,注意这边试了试发现是sha256(sha256(password + salt),不只是单层的
#!/bin/bash
# 优化版:使用hashcat的规则和组合模式
HASH="5d85b77d7d6d1a76cd589c3ba21d1839b1dd28568e39f1d2facc3a1b7d2e8bcb"
SALT="a3f8d9c2e1b4h7g6k9m2n5p8q1r4t7w"
echo "创建密码列表..."
# 生成所有可能的密码到文件
hashcat -a 3 "?l?l?d?d?d?d?d?d" --stdout > passwords.txt
echo "计算哈希..."
# 使用Python快速计算
python3 -c "
import hashlib
import sys
salt = '$SALT'
target = '$HASH'
with open('passwords.txt', 'r') as f:
for line in f:
password = line.strip()
# 第一层哈希
h1 = hashlib.sha256((password + salt).encode()).hexdigest()
# 第二层哈希
h2 = hashlib.sha256(h1.encode()).hexdigest()
if h2 == target:
print(f'密码找到: {password}')
with open('found.txt', 'w') as out:
out.write(password)
sys.exit(0)
print('未找到密码')
"
# 显示结果
if [ -f "found.txt" ]; then
echo -e "\n破解成功!密码: $(cat found.txt)"
else
echo -e "\n未找到密码"
fi
# 清理
rm -f passwords.txt found.txt

得到mb202623
即本题答案
24.分析周文杰Image.zip检材,周文杰在内部通联app中删除了几条聊天记录?[答案格式:123]
有了上一题这个密码就可以进行仿真登录了,这边讲一下如何仿真apk


先安装该软件

然后将该文件夹复制到MT文件夹共享目录下

接着在手机的MT管理器中左侧打开/storage/emulated/0/Pictures

右侧打开/data/data

这个时候长按这个左侧的文件夹复制到右边来


全部选择复制并替换

之后直接回到这边即可
用户名是zhouwenjie,密码是mb202623

成功仿真进入

成功仿真之后直接在里边找"已删除"即可,数一数就能发现是6条
25.分析周文杰Image.zip检材,聊天数据库中,显示聊天数据删除的是哪个字段?[答案格式:ab_cd]

这题可能有点争议吧,因为如果问的是整个对话删除的情况,那字段是is_deleted

如果是问的个别聊天记录删除情况,那就是extra_val,这边可以对照看见已经删除的内容,已删除的还会存在数据库里
26.分析林小婉手机检材,发现有个应用隐藏了很多信息,目前已经找到这个应用,它的程序名称是?[答案格式:微信]
因为给了附件,直接省略找程序这一步了

from androguard.core.apk import APK
apk = APK("1778310207366_9eee4f1c.apk")
print(apk.get_package())
print(apk.get_app_name())
print(apk.get_main_activity())
print(apk.get_files())
看看基础信息
package: com.jinritoutiao.news
appname: 今日头条
main activity: com.jinritoutiao.news.MainActivity
assets/vault_data.jth2
所以是今日头条
当然雷电可以直接一眼看见就是了

27.分析林小婉手机检材,这个应用有一个安全加密的PIN码,它是多少?[答案格式:12345678]
题目问PIN码,那就直接搜索PIN看看


反编译后可见 PIN 校验逻辑大致如下:
salt = "JinriPIN_Salt_"
expected = "7e881d49322271f3dd4fa24846a5cc53d1e0506b46007caa4b27f1416b27b54c"
algorithm = "PBKDF2WithHmacSHA256"
iterations = 10000
keyLength = 256
所以也就是说,这边我们的程序不是直接保存 PIN,而是将用户输入的 PIN 进行 PBKDF2-HMAC-SHA256 计算,再和固定 hash 比较
由于 PIN 长度还写死了限制为 8 位,可以写脚本爆破:
import hashlib
expected = "7e881d49322271f3dd4fa24846a5cc53d1e0506b46007caa4b27f1416b27b54c"
salt = b"JinriPIN_Salt_"
for i in range(100000000):
pin = f"{i:08d}"
h = hashlib.pbkdf2_hmac(
"sha256",
pin.encode(),
salt,
10000,
32
).hex()
if h == expected:
print(pin)
break
运行得到20150412,就是本题的答案了

上边那一个代码可能不太能爆破,太慢了,可以结合女儿生日来减少大量范围,从而好爆破很多
import hashlib
expected = "7e881d49322271f3dd4fa24846a5cc53d1e0506b46007caa4b27f1416b27b54c"
salt = b"JinriPIN_Salt_"
# 常见日期格式:年月日、月日年、日月年
formats = ['{year:04d}{month:02d}{day:02d}',
'{month:02d}{day:02d}{year:04d}',
'{day:02d}{month:02d}{year:04d}']
found = False
for year in range(2000, 2024): # 假设女儿生日在2000-2023之间
for month in range(1, 13):
# 每月天数
if month in (1,3,5,7,8,10,12):
day_max = 31
elif month in (4,6,9,11):
day_max = 30
else:
if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
day_max = 29
else:
day_max = 28
for day in range(1, day_max + 1):
for fmt in formats:
pin = fmt.format(year=year, month=month, day=day)
h = hashlib.pbkdf2_hmac("sha256", pin.encode(), salt, 10000, 32).hex()
if h == expected:
print(pin)
found = True
break
if found:
break
if found:
break
if found:
break

28.分析林小婉手机检材,这个应用隐藏的数据中,每个标签数据里,notes字段表示多少?[答案格式:事项]
题目说这个应用隐藏了数据,那我们先看看加密或隐藏的内容在哪里

解包apk发现了assets路径下存在有一个奇怪的文件,怀疑是加密文件,对此在jadx进行搜索


在这边我们发现了主密钥的生成逻辑:
password = "Jinritoutiao_Master_Key_2024_Secret"
salt = "JinriSalt_2024"
algorithm = "PBKDF2WithHmacSHA256"
iterations = 65536
keyLength = 256
使用AES/GCM/NoPadding加密算法
密钥派生PBKDF2算法迭代65536次,包含7个数据类
可以看到密钥都写在里边了,硬编码

根据代码可以看到前边12是IV,最后16是Tag,中间是密文,最后32位检验值

观察一下这个加密文件,文件头为JTH3,所以要跳开这个
所以文件结构可以整理为:
前 6 字节:magic/version
第 6 到 18 字节:IV,12 字节
中间部分:ciphertext
倒数 48 到倒数 32 字节:GCM tag,16 字节
最后 32 字节:校验值
解密脚本如下:
from pathlib import Path
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import hashlib
import json
data = Path("apk_extract/assets/vault_data.jth2").read_bytes()
key = hashlib.pbkdf2_hmac(
"sha256",
b"Jinritoutiao_Master_Key_2024_Secret",
b"JinriSalt_2024",
65536,
32
)
iv = data[6:18]
tag = data[-48:-32]
ciphertext = data[18:-48]
plaintext = AESGCM(key).decrypt(iv, ciphertext + tag, None)
Path("vault_decrypted.json").write_bytes(plaintext)
j = json.loads(plaintext.decode("utf-8"))
print(json.dumps(j, ensure_ascii=False, indent=2))
解密后得到 vault_decrypted.json

可以看到里边有很多关键词
在解密后的 JSON 中,多个数据结构都存在 notes 字段,例如:
{
"id": 1,
"name": "陈志远私人钱包",
"notes": "老板私人钱包"
}

可以看到大概就是备注的意思

搜索备注,发现UI 渲染代码 VaultScreenKt 真把notes作为备注
说明程序在界面上把 notes 显示为"备注"。
所以答案为备注
29.分析林小婉手机检材,这个应用隐藏的数据中,文件备份服务器的IP地址是多少?[答案格式:192.168.1.1]
在解密后的 vault_decrypted.json 中搜索文件备份服务器即可
可以看到 c2 字段中有如下数据:

因此文件备份服务器 IP 为192.168.1.200
30.分析林小婉手机检材,发现内部通联中财神撤回了一条消息,这个消息的内容是?[答案格式:盘古石杯。]
内部通联?依旧回到之前的social软件,太熟了,这边就不细说了

先找数据库

再找密码,还是一样掐头去尾,密码是s-dbw1776826167125Goo

确实有财神,还是跟刚刚一样的流程,先去除数据库密码再转csv文件

过滤财神

成功找到了撤回的那条消息
"把23年10月到24年6月的账本整理一下,发给我。"
31.分析林小婉手机检材,发现了账本,账本打开密码是什么?[答案格式:按实际填写]
继续查看解密后的vault_decrypted.json,问账本的密码,看看有没有账本和密码的内容
还真发现一条和龙腾项目账本相关的记录:

{
"id": 7,
"website": "龙腾项目账本",
"username": "",
"password": "DragonTeng@2024#$",
"notes": "账单复合.png打开密码"
}
同时在 core 字段中也有交叉印证:

{
"id": 6,
"category": "龙腾四海项目",
"title": "核心账本数据",
"content": "龙腾项目账本DragonTeng@2024#$",
"notes": "密码藏在账单复合.png图片中,需用LSB隐写提取"
}
两处都指向同一个密码,因此账本打开密码为:DragonTeng@2024#$