本文纯粹是为了弥补缺憾,而做的技术可行性验证,但如果对你有所启发,老苏会觉得非常欣慰,不感兴趣的朋友可以忽略
前言
2023
的最后一天,老苏发了一篇《将群晖IPTV后台管理套件docker化》,原本是想着通过 docker
,让其他平台的用户也能使用,结果只存活了几个小时就挂了
当时只完成了后台部分,配套的 Android
客户端还需要用户自己用 MT
管理器来修改 apk
中的后台地址。修改方法网上能找到教程,不过对于大部分用户来说,还是有门槛的。
而原本的套件安装完成时,会自动生成对应后台地址的安卓客户端文件。原理老苏大概是明白的,只是因为文章被下架,所以也就没了心情。
最近有点闲,重新研究了一下 Apktool
的编译和反编译,为了不搞乱环境,老苏还是习惯性的封装成了镜像。
原理
其实原理不复杂,第一步将未加固的 apk
文件进行反编译,第二步进行字符串替换,第三步重新编译,第四步重新签名。
思路理顺了,能少走点弯路。一开始老苏忘记了给编译出来的 apk
重新签名,结果导致生成的 apk
不能安装。加入了 Apksigner
之后,Dockerfile
几乎又重新重写了一遍,白白浪费了好几个小时。
构建镜像
如果你不想自己构建,可以跳过,直接阅读下一章节
先要准备三个文件,分别是 Dockerfile
、entrypoint.sh
和 generate-keystore.sh
,这三个文件放在同一个目录中,用于镜像的构建
文件都放在 Github
上,地址:https://github.com/wbsu2003/Dockerfile/tree/main/apktool
generate-keystore.sh
generate-keystore.sh
是用于生成签名证书的脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| openssl genrsa -out my-release-key.pem 2048
openssl req -new -x509 -key my-release-key.pem -out my-release-cert.pem -days 365 -subj "/CN=MyApp/C=US"
openssl pkcs12 -export -out "$KEYSTORE_PATH" -inkey my-release-key.pem -in my-release-cert.pem -name "my-key-alias" -passout pass:"$KEYSTORE_PASSWORD"
if [ -f $KEYSTORE_PATH ]; then echo "Keystore successfully created at $KEYSTORE_PATH" else echo "Failed to create keystore." fi
rm my-release-key.pem my-release-cert.pem
|
entrypoint.sh
entrypoint.sh
则完成了整个原理中描述的的 4
步流程
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 62 63 64
| #!/bin/bash
# 生成证书 if [ "$GENERATE_KEYSTORE" = "true" ]; then echo "生成 keystore..." if [ -z "$KEYSTORE_PASSWORD" ]; then echo "请设置 KEYSTORE_PASSWORD 环境变量。" exit 1 fi /usr/local/bin/generate-keystore.sh fi
# 检查 keystore 文件是否存在 if [ ! -f "$KEYSTORE_PATH" ]; then echo "Error: Keystore file $KEYSTORE_PATH does not exist." exit 1 else echo "Keystore file found: $KEYSTORE_PATH" fi
# 检查 apksigner 是否可用 if ! command -v apksigner &> /dev/null; then echo "apksigner 未找到,请检查 Android SDK 是否正确安装。" exit 1 fi
# 检查环境变量是否设置 if [[ -z "$APP_URL" || -z "$NEW_APP_URL" || -z "$ORIGINAL_APK_NAME" ]]; then echo "请设置环境变量 APP_URL、NEW_APP_URL 和 ORIGINAL_APK_NAME" exit 1 fi
# 定义输入和输出路径 INPUT_DIR="/app/input" OUTPUT_DIR="/app/output" ORIGINAL_APK="$INPUT_DIR/$ORIGINAL_APK_NAME"
# 创建 decompiled 目录 DECOMPILED_DIR="/app/decompiled" if [ -d "$DECOMPILED_DIR" ]; then rm -rf "$DECOMPILED_DIR" fi
# 提取原 APK 的签名信息 SIGNATURE_INFO=$(apksigner verify --print-signature "$ORIGINAL_APK") echo "原 APK 签名信息:" echo "$SIGNATURE_INFO"
# 反编译 APK apktool d "$ORIGINAL_APK" -o "$DECOMPILED_DIR"
# 替换字符串 find "$DECOMPILED_DIR" -type f -exec sed -i "s|$APP_URL|$NEW_APP_URL|g" {} +
# 重新编译 APK apktool b "$DECOMPILED_DIR" -o "$OUTPUT_DIR/${ORIGINAL_APK_NAME%.apk}_new.apk"
# 使用 PKCS#12 keystore 对新 APK 进行签名 if apksigner sign --ks "$KEYSTORE_PATH" --ks-type PKCS12 --ks-pass pass:"$KEYSTORE_PASSWORD" --key-pass pass:"$KEYSTORE_PASSWORD" --out "$OUTPUT_DIR/${ORIGINAL_APK_NAME%.apk}_new_signed.apk" "$OUTPUT_DIR/${ORIGINAL_APK_NAME%.apk}_new.apk"; then echo "新 APK 已生成并输出到 $OUTPUT_DIR/${ORIGINAL_APK_NAME%.apk}_new_signed.apk" else echo "签名失败,未生成新 APK。" exit 1 fi
|
Dockerfile
Dockerfile
完成整个运行环境的搭建
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
| FROM openjdk:8-jdk-slim
RUN apt-get update && apt-get install -y \ wget \ unzip \ openssl \ && apt-get clean
RUN wget -q https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip -O sdk-tools.zip \ && unzip sdk-tools.zip -d /android-sdk \ && rm sdk-tools.zip
ENV ANDROID_SDK_ROOT=/android-sdk ENV PATH=$PATH:$ANDROID_SDK_ROOT/tools/bin:$ANDROID_SDK_ROOT/build-tools/29.0.3
ENV GENERATE_KEYSTORE="true" ENV KEY_ALIAS="my-key-alias" ENV KEYSTORE_PASSWORD="123456" ENV KEYSTORE_PATH="/app/my-release-key.keystore"
RUN yes | /android-sdk/tools/bin/sdkmanager --licenses \ && /android-sdk/tools/bin/sdkmanager "build-tools;29.0.3" "platform-tools" \ && apt-get remove --purge -y wget unzip \ && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y curl \ && curl -L -o /usr/local/bin/apktool.jar https://github.com/iBotPeaches/Apktool/releases/download/v2.10.0/apktool_2.10.0.jar \ && echo -e '#!/bin/sh\nexec java -jar /usr/local/bin/apktool.jar "$@"' > /usr/local/bin/apktool \ && chmod +x /usr/local/bin/apktool
WORKDIR /app
COPY generate-keystore.sh /usr/local/bin/generate-keystore.sh RUN chmod +x /usr/local/bin/generate-keystore.sh
COPY entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
构建镜像和容器运行的基本命令如下👇
1 2 3 4 5 6 7 8 9 10
| mkdir -p apktool
cd apktool
docker build -t wbsu2003/apktool:v1 .
|
使用
这个镜像是命令行运行的,没有 Web
界面
还是以在群晖上运行为例
在 docker
文件夹中,创建一个新文件夹 apktool
,并在其中建两个子文件夹 input
和 output
文件夹 |
装载路径 |
说明 |
docker/apktool/input |
/app/input |
存放待处理的 apk 文件 |
docker/apktool/output |
/app/output |
存放处理之后的 apk 文件 |
1 2 3 4 5
| mkdir -p /volume1/docker/apktool/{input,output}
cd /volume1/docker/apktool
|
将需要处理的 apk
文件放入到 input
目录中
示例文件老苏放在了:https://github.com/wbsu2003/Dockerfile/raw/refs/heads/main/apktool/QHTV.apk,用手机安装会提示风险,可能是因为签名是 V1
的缘故;
现在可以在当前目录中执行下面的命令了
1 2 3 4 5 6 7 8 9 10 11
| docker run --rm \ -v $(pwd)/input:/app/input \ -v $(pwd)/output:/app/output \ -e APP_URL="http://192.168.0.199/iptv" \ -e NEW_APP_URL="http://192.168.0.197:3332" \ -e ORIGINAL_APK_NAME="QHTV.apk" \ -e GENERATE_KEYSTORE="true" \ -e KEYSTORE_PASSWORD="123456" \ -e KEYSTORE_PATH="/app/my-release-key.keystore" \ wbsu2003/apktool
|
第一次运行,会需要下载镜像
可变 |
值 |
APP_URL |
指程序中原来的后台地址 |
NEW_APP_URL |
指用于替换的新的后台地址 |
ORIGINAL_APK_NAME |
指需要替换的 apk 的文件名,该文件应该被放在 input 目录中 |
GENERATE_KEYSTORE |
是否要生成证书,默认为 true |
KEYSTORE_PASSWORD |
用于保护 keystore 的密码,脚本会使用这个密码生成和访问 keystore |
KEYSTORE_PATH |
证书的路径,默认为 /app/my-release-key.keystore ,不建议改 |
前 3
个为必填项,后 3
个为可选项
当然也可以省掉 KEYSTORE
相关的三个变量,因为老苏在 Dockerfile
中赋了默认值
1 2 3 4 5 6 7 8
| docker run --rm \ -v $(pwd)/input:/app/input \ -v $(pwd)/output:/app/output \ -e APP_URL="http://192.168.0.199/iptv" \ -e NEW_APP_URL="http://192.168.0.197:3332" \ -e ORIGINAL_APK_NAME="QHTV.apk" \ wbsu2003/apktool
|
但 GENERATE_KEYSTORE
如果赋值为 false
,是不会往下执行的
1 2 3 4 5 6 7 8 9
| docker run --rm \ -v $(pwd)/input:/app/input \ -v $(pwd)/output:/app/output \ -e APP_URL="http://192.168.0.199/iptv" \ -e NEW_APP_URL="http://192.168.0.197:3332" \ -e ORIGINAL_APK_NAME="QHTV.apk" \ -e GENERATE_KEYSTORE="false" \ wbsu2003/apktool
|
如果没有出错的话
可以在 output
目录看到两个文件
其中 QHTV_new_signed.apk
就是我们用来安装的文件,它的签名状态已经改为了 V1+V2+V3
其他
目前老苏发现的问题:
如果你先安装了 QHTV.apk
文件,再安装 QHTV_new_signed.apk
会提示安装不了,必须先卸载 QHTV.apk
才行,这是因为签名文件变了。老苏采用的是动态生成签名,所以每次执行,签名都是不一样的;
生成的 QHTV_new_signed.apk
在 HarmonyOS 2.0.0
和电视盒子上运行正常,但是在另一台 Android 14
上,则卡在首界面,后台授权中查不到上线信息,因此也就无法授权,这个已经超出了本文的范围;
另外请不要问关于 《将群晖IPTV后台管理套件docker化》的问题,当时就是为了不想做技术支持,专门设置成了付费文章,谢谢~
参考文档
Apktool | Apktool
地址:https://apktool.org/
iBotPeaches/Apktool: A tool for reverse engineering Android apk files
地址:https://github.com/iBotPeaches/Apktool
群晖NAS安装IPTV管理系统套件 图文教程 – WANG
地址:https://www.oureiq.top:8812/2023/01/29/群晖nas安装iptv管理系统套件-图文教程/