Logo
Overview

杂交linux?

April 11, 2026
浏览量--

在推文中我提到了一嘴,研究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了。

DOCKER
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 --from=rootfs /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

DOCKER
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 --from=mp4box /out/ /

ENTRYPOINT ["/usr/bin/MP4Box"]
CMD ["-version"]

经过测试这也能运行。

于是我们只需要把刚刚的两个结合到一起,就能获得glibc和bionic libc共存的程序。

细节代码见

这就是神奇的linux魅力啊,哪怕最后删到一个文件没有里它还是能正常运行的。

评论加载中...