在推文中我提到了一嘴,研究apple music里提到的各种so,可以帮助我们获得最接近原生的登录方法。
并且看到了这样一个wrapper程序用来下载apple music。但是它用了两套二进制,一个放在外面不用ndk,纯glibc,另一个放在chroot里,ndk的实现用来适配android环境。这是必须的吗?
FROM scratch
显然不是,我们知道,docker实际上可以被理解成一个分层的沙盘。在Dockerfile构建时,每一个命令(RUN,COPY,..etc)都是一层,每层中保存了这一层被放进来的内容。这样的增量保存是Docker广受好评的原因。那么,最开始的系统镜像是怎么打包的呢?
答案就是本节标题`FROM scratch`,这里会给一个空白的沙盘,文件系统为空。所以我们只需要把wrapper/rootfs直接放进根目录,我们就不再需要一个单独的wrapper来启动chroot了。
FROM debian:bookworm-slim AS rootfs
ARG ROOTFS_ARCHIVE_URL="https://github.com/WorldObservationLog/wrapper/archive/refs/heads/main.tar.gz"
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl tar \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /tmp/rootfs /out \
&& curl -fsSL "$ROOTFS_ARCHIVE_URL" -o /tmp/rootfs/rootfs.tar.gz \
&& tar -xzf /tmp/rootfs/rootfs.tar.gz -C /tmp/rootfs \
&& src_dir="$(find /tmp/rootfs -mindepth 2 -maxdepth 2 -type d -name rootfs | head -n 1)" \
&& test -n "$src_dir" \
&& cp -a "$src_dir" /out/rootfs
FROM scratch
COPY /out/rootfs / 这样就构造了一个“伪”android环境。于是我们写一个简单程序放进去就能用了!
但是这还不是我想要的效果。可以看到另一个程序apple-music-downloader中用到了mp4box这种重依赖glibc的程序。大家第一想法显然都应该是连接成static放到容器中就能用了。但是,能不能不这样呢?
INTERPRETER,LOADER
这里是linux的另一个有意思的点。在启动linux程序时,走exec*的系统调用来启动。启动时,linux会检查程序的头标志符,如果存在PT_INTERP说明程序依赖动态加载的loader。在glibc环境下是/lib64/ld-linux-x86-64.so.2,在android上是/system/bin/loader64。内核会启动这个程序来进行依赖的加载。最后再运行程序。
所以在我们打ctf的时候会用到patchelf这个程序来修改程序头,让它使用我们期望的libc。
不过patchelf是给nixos写的谁能想到…
所以怎么让glibc和bionic libc共存呢?
实际上我们只需要把两边都loader和so文件都放进来到对应的位置,实际上直接运行就能运行了。位置
下面是我写的测试Dockerfile
FROM debian:bookworm-slim AS mp4box
ARG GPAC_APT_URI="https://dist.gpac.io/gpac/linux/debian"
ARG GPAC_APT_COMPONENT="nightly"
RUN apt-get update \
&& set -eux \
&& apt-get install -y --no-install-recommends \
ca-certificates \
curl \
binutils \
&& install -m 0755 -d /etc/apt/keyrings \
&& curl -fsSL https://dist.gpac.io/gpac/linux/gpg.asc -o /etc/apt/keyrings/gpac.asc \
&& chmod a+r /etc/apt/keyrings/gpac.asc \
&& printf 'Types: deb\nURIs: %s\nSuites: %s\nComponents: %s\nSigned-By: /etc/apt/keyrings/gpac.asc\n' \
"$GPAC_APT_URI" \
"$(. /etc/os-release && echo "$VERSION_CODENAME")" \
"$GPAC_APT_COMPONENT" \
> /etc/apt/sources.list.d/gpac.sources \
&& apt-get update \
&& apt-get install -y --no-install-recommends gpac \
&& rm -rf /var/lib/apt/lists/* \
&& mp4box_path="$(command -v MP4Box || command -v mp4box)" \
&& test -x "$mp4box_path" \
&& libgpac_path="$(find /usr/lib /lib -name 'libgpac.so*' ! -name '*.a' | sort | head -n 1)" \
&& test -n "$libgpac_path" \
&& test -e "$libgpac_path" \
&& interp="$(readelf -l "$mp4box_path" | sed -n 's/.*Requesting program interpreter: \(.*\)]/\1/p')" \
&& test -n "$interp" \
&& module_paths="$(if test -d /usr/lib/gpac; then find /usr/lib/gpac -type f -name '*.so' | sort; fi)" \
&& deps="$( \
{ \
ldd "$mp4box_path" "$libgpac_path"; \
if test -n "$module_paths"; then ldd $module_paths; fi; \
} | awk ' \
/=> \// { print $3 } \
/^\// && $1 !~ /:$/ { print $1 } \
' \
| sort -u \
)" \
&& install -d /out/usr/bin /out/usr/lib /out/lib64 /out/etc/ssl/certs \
&& ln -s usr/lib /out/lib \
&& cp -a "$mp4box_path" /out/usr/bin/MP4Box \
&& if test -e /usr/bin/mp4box; then cp -a /usr/bin/mp4box /out/usr/bin/mp4box; fi \
&& if test -d /usr/lib/gpac; then cp -a /usr/lib/gpac /out/usr/lib/gpac; fi \
&& for dep in "$interp" "$libgpac_path" $deps; do \
clean_dep="${dep%:}"; \
real="$(readlink -f "$clean_dep")"; \
install -d "/out$(dirname "$real")"; \
cp -a "$real" "/out$real"; \
if test -L "$clean_dep"; then \
case "$clean_dep" in \
/lib/*) \
link_path="/usr$clean_dep"; \
;; \
*) \
link_path="$clean_dep"; \
;; \
esac; \
install -d "/out$(dirname "$link_path")"; \
cp -a "$clean_dep" "/out$link_path"; \
fi; \
done \
&& for alt in \
/etc/alternatives/libblas.so.3-x86_64-linux-gnu \
/etc/alternatives/liblapack.so.3-x86_64-linux-gnu; do \
if test -L "$alt"; then \
alt_target="$(readlink "$alt")"; \
install -d "/out$(dirname "$alt")" "/out$(dirname "$alt_target")"; \
cp -a "$alt" "/out$alt"; \
cp -a "$alt_target" "/out$alt_target"; \
fi; \
done \
&& if test -f /etc/ssl/certs/ca-certificates.crt; then \
cp -a /etc/ssl/certs/ca-certificates.crt /out/etc/ssl/certs/ca-certificates.crt; \
fi
FROM scratch
COPY /out/ /
ENTRYPOINT ["/usr/bin/MP4Box"]
CMD ["-version"]
经过测试这也能运行。
于是我们只需要把刚刚的两个结合到一起,就能获得glibc和bionic libc共存的程序。
细节代码见
这就是神奇的linux魅力啊,哪怕最后删到一个文件没有里它还是能正常运行的。