【问题标题】:Extract binary payload from shell script received from stdin从从标准输入接收到的 shell 脚本中提取二进制有效负载
【发布时间】:2020-07-26 15:27:50
【问题描述】:

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

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

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

在我的特殊情况下,为了进一步自动化,我使用| sh - 模式,但它破坏了有效负载提取,因为pipes are not seekable

我还尝试将二进制有效负载嵌入到一个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

Bash 抱怨: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

【问题讨论】:

  • exec tar zxvf 为什么要执行? But it makes shells (both bash and NetBSD's /bin/sh) confused什么?当外壳“混乱”时是什么意思?我想 tar -xzvf - &lt;&lt;'EOF'EOF 随机 uuid if there is som...data received from stdin. 我不明白 - 这不是 XY 问题吗?您真正要解决的问题是什么?您确实没有发布了一个从标准输入读取脚本和数据的示例 - 两个代码 sn-ps 都完全忽略标准输入,并且“二进制数据”包含在文件中。
  • 我编辑了问题并在最后添加了一个澄清说明。
  • 是的,这就是为什么引用此处的文档分隔符。如果您不想扩展此处的文档内容,请执行&lt;&lt;'EOF'&lt;&lt;"EOF"not four. 我假设您希望它按字面意思输出 $(echo 0a0a0a0a | xxd -p -r)。我认为你的问题太宽泛了,你问的是“一些 shell 巫术”,这对于一个 stackoverflow 问题来说太宽泛了。
  • 我也尝试了用引号分隔的heredoc(我添加了四个文字0a——在vim中它们显示为^@),结果是一样的:输出只有0a。文件/tmp/out 大小只有一个字节。

标签: bash shell sh stdin


【解决方案1】:

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 可能是最安全的,所以它不会对输入字符串中的非有效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;
}

【讨论】:

    【解决方案2】:

    如果有一些 shell 魔法可以用来从标准输入接收脚本和二进制数据,并从标准输入接收到的数据中提取二进制数据。

    有这样的脚本:

    cat <<'EOF' >script.sh
    #!/bin/sh
    hostname
    echo "What is you age?"
    if ! IFS= read -r ans; then
         echo "Read failed!"
    else 
         echo "You are $ans years old."
    fi
    xxd -p
    EOF
    

    您可以通过进程替换来管道到远程 ssh shell,这里的文档后跟您想要的任何数据:

    {
       echo 123
       echo "This is the input"
       echo 001122 | xxd -r -p
    } | {
        u=$(uuidgen)
        # Remove shell is started with a process subtitution
        # terminated with a unique mark
        echo "bash <(cat <<'$u'"
        cat script.sh
        # Note - script.sh may not read all input
        # which will then executed as commands
        # read it here and make sure nothing leaks
        echo 'cat >/dev/null'
        echo "$u"
        echo ")"
        # the process substitution is followed by input
        # note that because the upper bash "eats" all input
        # it will not execute.
        cat
    } | ssh host
    

    示例执行:

    host
    What is you age?
    You are 123 years old.
    546869732069732074686520696e7075740a001122
    

    你 否:

    我真正的问题是:我正在动态生成用于远程配置管理的 shell 脚本,所以我使用 createsh | ssh 主机。我将二进制数据嵌入到 shell 脚本中,以便可以将其提取到远程主机。

    虽然您可以使用分隔符分隔两个流:

    u=$(uuidgen); cat script.sh; echo; echo $u; cat binarydata.txt | ssh host bash -c 'sed "/$1/{d;q}" >script.sh; cat > binarydata.txt' _ "$u"
    

    这只是重新发明轮子 - 它已经存在并被称为tar

    tar -cf - script.sh binarydata.txt | ssh host bash -c 'cd /tmpdir; <unpack tar>; ./script.sh binarydata.txt; rm /tmpdir'
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-10-07
      • 2012-11-14
      • 2011-02-20
      • 1970-01-01
      • 2010-10-07
      • 2021-03-29
      • 1970-01-01
      • 2012-04-15
      相关资源
      最近更新 更多