首页 > 解决方案 > 从从标准输入接收到的 shell 脚本中提取二进制有效负载

问题描述

如果您是使用以下技术的 shell 脚本文件,则可以从其中提取任何有效负载(请参阅this):

#!/bin/sh
tail -n +4 > package.tgz
exec tar zxvf package.tgz
# payload comes here...

这需要一个文件,因此tail可以将文件查找到正确的位置。

在我的特殊情况下,为了进一步自动化,我使用了该| sh -模式,但它破坏了有效负载提取,因为管道不可搜索

我还尝试将二进制有效负载嵌入到heredoc中,这样我就可以制作如下内容:

cat >package.tgz <<END
# payload comes here
END
tar zxvf package.tgz

但它使 shell(bash 和 NetBSD 的 /bin/sh)感到困惑,而且它不起作用。

我可以在 heredoc 中使用 uuencode 或 base64,但我只是想知道是否有一些 shell 魔法可用于从标准输入接收脚本和二进制数据,并从标准输入接收的数据中提取二进制数据。

编辑:

当我的意思是外壳变得混乱时,我的意思是它可以忽略空字节或具有未定义的行为,即使在 heredoc 中也是如此。尝试:

cat > /tmp/out <<EOF
$(echo 410041 | xxd -p -r)
EOF
xxd -p /tmp/out

巴什抱怨:line 2: warning: command substitution: ignored null byte in input

如果我从字面上将十六进制字节嵌入410041到 shell 脚本中并使用引用的 heredoc,结果会有所不同,但 bash 只会删除空字节。

echo '#!/bin/sh' > foo.sh
echo "cat > /tmp/out <<'EOF'" >> foo.sh
echo 410041 | xxd -p -r >> foo.sh
echo >> foo.sh
echo EOF >> foo.sh
echo 'xxd -p /tmp/out' >> foo.sh
bash /tmp/foo.sh 
41410a

标签: bashshellshstdin

解决方案


bash(和其他 shell)倾向于在 C 字符串中“思考”,这些字符串以空字符结尾,因此不能包含空字符(这就是字符串结尾的意思)。要产生空值,您几乎必须运行一些程序/命令,这些程序/命令采用一些安全编码的内容并产生空值,并将其输出直接发送到文件或管道,而不需要外壳在两者之间查看。

最简单的方法是使用 base64 之类的东西对文件进行编码,然后将输出从base64 -D. 像这样的东西:

base64 -D <<'EOF' | tar xzv
H4sIAOzIHV8AA+y9DVxVVbowvs/hgAc8sY+Jhvl1VCoJBVQsETVgOIgViin2pSkq
....
EOF

如果您不想使用 base64,另一种选择是使用 bash 的printf内置函数将包含 null 或其他奇怪的输出打印到管道。它可能看起来像这样:

LC_ALL=C
printf '\037\213\010\000\354\310\035_\000\003\354\275\015\\UU....' | tar xzv

在上面的示例中,我将所有不可打印的 ASCII 转换为\octal代码。实际上应该可以将几乎所有内容都包含为文字字符,除了 null、单引号(不能包含在单引号字符串中,可能最简单的八进制编码)、反斜杠(只需加倍)和百分号 (也加倍)。我不认为这会是一个问题,但首先设置可能是最安全的LC_ALL=C,所以它不会对输入字符串中的 non-valid-UTF-8 感到害怕。

这是一个快速而肮脏的 C 程序来进行编码。请注意,它将输出发送到标准输出,并且可能包含会弄乱您的终端的垃圾;所以一定要在某处直接输出。

#include <stdio.h>
#include <stdlib.h>

int main( int argc, char *argv[] )  {
    int ch;
    FILE *fp;

    if ( argc != 2 ) {
        fprintf(stderr, "Usage: %s infile\n", argv[0]);
        return 1;
    }

    fp = fopen(argv[1], "r");
    if (fp == NULL) {
        fprintf(stderr, "Error opening %s", argv[1]);
        return 1;
    }

    printf("#!/bin/bash\nLC_ALL=C\nprintf '");

    while((ch = fgetc(fp)) != EOF) {
        switch(ch) {
            case '\000':
                printf("\\000");
                break;
            case '\047':
                printf("\\047");
                break;
            case '%':
            case '\\':
                printf("%c%c", ch, ch);
                break;
            default:
                printf("%c", ch);
        }
    }
    fclose(fp);

    printf("' | tar xzv\n");
    return 0;
}

推荐阅读