使用此脚本在二进制文件上运行命令

尝试这个简单的脚本,以便轻松地在二进制文件上运行命令,无论它们是如何打包的。
94 位读者喜欢这个。
Binary code on a computer screen

公共领域,通过 LibreShot

从命令行检查文件通常是一件容易的事情。您只需运行您想要的命令,后跟要检查的文件列表。然而,处理二进制文件则更为复杂。这些文件通常被打包成档案、tarball 或其他打包格式。run-on-binaries 脚本提供了一种方便的方法,可以在文件集合上运行命令,无论它们是如何打包的。

脚本的调用非常简单

run-on-binaries <file(s}>

例如

run-on-binaries /usr/bin/ls foo.rpm

将列出 foo.rpm 文件内的所有文件,而

run-on-binaries /usr/bin/readelf -a libc.a

将在 libc.a 库内的所有目标文件上运行带有 -a 命令行选项的 readelf 程序。

如有必要,该脚本可以传递一个包含要处理的其他文件列表的文件,而不是在命令行上指定它们——像这样

run-on-binaries --files-from=foo.lst /usr/bin/ps2ascii

这将对 foo.lst 中列出的所有文件运行 ps2ascii 脚本。(文件只需要用空格分隔。如果需要,可以在单行上包含多个文件)。

此外,可以提供一个跳过列表,以阻止脚本处理指定的文件

run-on-binaries --skip-list=skip.lst /usr/bin/wc *

这将对当前目录中的所有文件运行 wc 程序,但 skip.lst 中指定的文件除外。

该脚本不会递归到目录中,但这可以通过将其与 find 命令结合使用来处理,如下所示

find . -type f -exec run-on-binaries @ ;

或者

find . -type d -exec run-on-binaries @/* ;

这两个调用之间的唯一区别是,第二个调用每个目录只运行一次目标程序,但为其提供一个包含目录中所有文件的长命令行。

虽然方便,但该脚本在几个方面存在不足。目前,它不检查 PATH 环境变量来查找被要求运行的命令,因此必须提供完整路径。此外,脚本应该能够自行处理递归,而无需 find 命令的帮助。

run-on-binaries 脚本是 annobin 包的一部分,该包在 Fedora 上可用。annobin 的源代码也可以从 git 仓库 git://sourceware.org/git/annobin.git 获取。

脚本

#!/bin/bash

# Script to run another script/program on the executables inside a given file.
#
# Created by Nick Clifton.  <nickc@redhat.com>
# Copyright (c) 2018 Red Hat.
#
# This is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published
# by the Free Software Foundation; either version 3, or (at your
# option) any later version.

# It is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# Usage:
#   run-on-binaries-in [options] program [options-for-the-program] file(s)
#
# This script does not handle directories.  This is deliberate.
# It is intended that if recursion is needed then it will be
# invoked from find, like this:
#
#   find . -name "*.rpm" -exec run-on-binaries-in <script-to-run> {} \;


version=1.0

help ()
{
  # The following exec goop is so that we don't have to manually
  # redirect every message to stderr in this function.
  exec 4>&1    # save stdout fd to fd #4
  exec 1>&2    # redirect stdout to stderr

  cat <<__EOM__

This is a shell script to run another script/program on one or more binary
files.  If the file(s) specified are archives of some kind (including rpms)
then the script/program is run on the binary executables inside the archive.

Usage: $prog {options} program {options-for-the-program} files(s)

  {options} are:
  -h         --help               Display this information and then exit.
  -v         --version            Report the version number of this script.
  -V         --verbose            Report on progress.
  -q         --quiet              Do not include the script name in the output.
  -i         --ignore             Silently ignore files that are not executables or archives.
  -p=<TEXT>  --prefix=<TEXT>      Prefix normal output with this string.
  -t=<DIR>   --tmpdir=<DIR>       Temporary directory to use when opening archives.
  -f=<FILE>  --files-from=<FILE>  Process files listed in <FILE>.
  -s=<FILE>  --skip-list=<FILE>   Skip any file listed in <FILE>.
  --                              Stop accumulating options.

Examples:

  $prog hardened foo.rpm
                              Runs the hardened script on the executable
                              files inside foo.rpm.

  $prog check-abi -v fred.tar.xz
                              Runs the check-abi script on the decompressed
                              contents of the fred.tar.xz archive, passing the
                              -v option to check-abi as it does so.      

  $prog -V -f=list.txt readelf -a
                              Runs the readelf program, with the -a option on
                              every file listed in the list.txt.  Describes
                              what is being done as it works.

  $prog -v -- -fred -a jim -b bert -- -c harry
                              Runs the script "-fred" on the files jim, bert,
                              "-c" and harry.  Passes the options "-a" and
                              "-b" to the script (even when run on jim).
                              Reports the version of this script as well.

__EOM__
  exec 1>&4   # Copy stdout fd back from temporary save fd, #4
}

main ()
{
    init
    
    parse_args ${1+"$@"}

    if [ $failed -eq 0 ];
    then
	run_script_on_files
    fi

    if [ $failed -ne 0 ];
    then
	exit 1
    else
	exit 0
    fi
}

report ()
{
    if [ $quiet -eq 0 ];
    then
	echo -n $prog": "
    fi
    
    echo ${1+"$@"}
}

ice ()
{
    report "Internal error: " ${1+"$@"}
    exit 1
}

fail ()
{
    report "Failure:" ${1+"$@"}
    failed=1
}

verbose ()
{
    if [ $verbose -ne 0 ]
    then
	report ${1+"$@"}
    fi
}

# Initialise global variables.
init ()
{
    files[0]="";  
    # num_files is the number of files to be scanned.
    # files[0] is the script to run on the files.
    num_files=0;

    script=""
    script_opts="";

    prog_opts="-i"

    tmpdir=/dev/shm
    prefix=""    
    files_from=""
    skip_list=""

    failed=0
    verbose=0
    ignore=0
    quiet=0
}

# Parse our command line
parse_args ()
{
    abs_prog=$0;
    prog=`basename $abs_prog`;

    # Locate any additional command line switches
    # Likewise accumulate non-switches to the files list.
    while [ $# -gt 0 ]
    do
	optname="`echo $1 | sed 's,=.*,,'`"
	optarg="`echo $1 | sed 's,^[^=]*=,,'`"
	case "$optname" in
	    -v | --version)
		report "version: $version"
		;;
	    -h | --help)
		help
		exit 0
		;;
	    -q | --quiet)
		quiet=1;
		prog_opts="$prog_opts -q"
		;;
	    -V | --verbose)
		if [ $verbose -eq 1 ];
		then
		    # This has the effect of cancelling out the prog_opts="-i"
		    # in the init function, so that recursive invocations of this
		    # script will complain about unrecognised file types.
		    if [ $quiet -eq 0 ];
		    then
			prog_opts="-V -V"
		    else
			prog_opts="-V -V -q"
		    fi
		else
		    verbose=1;
		    prog_opts="$prog_opts -V"
		fi
		;;
	    -i | --ignore)
		ignore=1
		;;
	    -t | --tmpdir)
		if test "x$optarg" = "x$optname" ;
		then
		    shift
		    if [ $# -eq 0 ]
		    then
			fail "$optname needs a directory name"
		    else
			tmpdir=$1
		    fi
		else
		    tmpdir="$optarg"
		fi
		;;
	    -p | --prefix)
		if test "x$optarg" = "x$optname" ;
		then
		    shift
		    if [ $# -eq 0 ]
		    then
			fail "$optname needs a string argument"
		    else
			prefix=$1
		    fi
		else
		    prefix="$optarg"
		fi
		;;
	    -f | --files_from)
		if test "x$optarg" = "x$optname" ;
		then
		    shift
		    if [ $# -eq 0 ]
		    then
			fail "$optname needs a file name"
		    else
			files_from=$1
		    fi
		else
		    files_from="$optarg"
		fi
		;;
	    
	    -s | --skip-list)
		if test "x$optarg" = "x$optname" ;
		then
		    shift
		    if [ $# -eq 0 ]
		    then
			fail "$optname needs a file name"
		    else
			skip_list=$1
		    fi
		else
		    skip_list="$optarg"
		fi
		;;
	    
	    --)
		shift
		break;
		;;
	    --*)
		fail "unrecognised option: $1"
		help
		;;
	    *)
		script="$1";
		if ! [ -a "$script" ]
		then
		    fail "$script: program/script not found"
		elif  ! [ -x "$script" ]
		then
		    fail "$script: program/script not executable"
		fi
		# After we have seen the first non-option we stop
		# accumulating options for this script and instead
		# start accumulating options for the script to be
		# run.
		shift
		break;
		;;
	esac
	shift
    done

    # Read in the contents of the --file-from list, if specified.
    if test "x$files_from" != "x" ;
    then
	if ! [ -a "$files_from" ]
	then
	    fail "$files_from: file not found"
	elif ! [ -r "$files_from" ]
	then
	    fail "$files_from: file not readable"
	else
	    eval 'files=($(cat $files_from))'
	    num_files=${#files[*]}
	fi
    fi
    skip_files[foo]=bar

    # Check that the skip list exists, if specified.
    if test "x$skip_list" != "x" ;
    then
	if ! [ -a "$skip_list" ]
	then
	    fail "$skip_list: file not found"
	elif ! [ -r "$skip_list" ]
	then
	    fail "$files_from: file not readable"
	fi
    fi

    # Accumulate any remaining arguments separating out the arguments
    # for the script from the names of the files to scan.
    while [ $# -gt 0 ]
    do
	optname="`echo $1 | sed 's,=.*,,'`"
	optarg="`echo $1 | sed 's,^[^=]*=,,'`"
	case "$optname" in
	    --)
		shift
		break;
		;;
	    -*)
		script_opts="$script_opts $1"
		;;
	    *)
		files[$num_files]="$1";
		let "num_files++"
		;;
	esac
	shift
    done

    # Accumulate any remaining arguments without processing them.
    while [ $# -gt 0 ]
    do
	files[$num_files]="$1";
	let "num_files++";
	shift
    done

    if [ $num_files -gt 0 ];
    then
	# Remember that we are counting from zero not one.
	let "num_files--"
    else
	fail "Must specify a program/script and at least one file to scan."
    fi
}

run_script_on_files ()
{
    local i

    i=0;
    while [ $i -le $num_files ]
    do
	run_on_file i
	let "i++"
    done
}

# syntax: run <command> [<args>]
#  If being verbose report the command being run, and
#   the directory in which it is run.
run ()
{
  local where

  if test "x$1" = "x" ;
  then
    fail "run() called without an argument."
  fi

  verbose "  Running: ${1+$@}"

  ${1+$@}
}

decompress ()
{
    local abs_file decompressor decomp_args orig_file base_file

    # Paranoia checks - the user should never encounter these.
    if test "x$4" = "x" ;
    then
	ice "decompress called with too few arguments"
    fi
    if test "x$5" != "x" ;
    then
	ice "decompress called with too many arguments"
    fi

    abs_file=$1
    decompressor=$2
    decomp_args=$3
    orig_file=$4

    base_file=`basename $abs_file`

    run cp $abs_file $base_file
    run $decompressor $decomp_args $base_file
    if [ $? != 0 ];
    then
	fail "$orig_file: Unable to decompress"
    fi

    rm -f $base_file
}

run_on_file ()
{
    local file

    # Paranoia checks - the user should never encounter these.
    if test "x$1" = "x" ;
    then
	ice "scan_file called without an argument"
    fi
    if test "x$2" != "x" ;
    then
	ice "scan_file called with too many arguments"
    fi

    # Use quotes when accessing files in order to preserve
    # any spaces that might be in the directory name.
    file="${files[$1]}";

    # Catch names that start with a dash - they might confuse readelf
    if test "x${file:0:1}" = "x-" ;
    then
	file="./$file"
    fi

    # See if we should skip this file.
    if test "x$skip_list" != "x" ;
    then
	# This regexp looks for $file being the first text on a line, either
	# on its own, or with additional text separated from it by at least
	# one space character.  So searching for "fred" in the following gives:
	#  fr         <- no match
	#  fred       <- match
	#  fredjim    <- no match
	#  fred bert  <- match
	regexp="^$file[^[:graph:]]*"
	grep --silent --regexp="$regexp" $skip_list
	if [ $? = 0 ];
	then
	    verbose "$file: skipping"
	    return
	fi
    fi

    # Check the file.
    if ! [ -a "$file" ]
    then
	fail "$file: file not found"
	return
    elif ! [ -r "$file" ]
    then
	if [ $ignore -eq 0 ];
	then
	    fail "$file: not readable"
	fi
	return
    elif [ -d "$file" ]
    then
	if [ $ignore -eq 0 ];
	then
	    if [ $num_files -gt 1 ];
	    then
		verbose "$file: skipping - it is a directory"
	    else
		report "$file: skipping - it is a directory"
	    fi
	fi
	return
    elif ! [ -f "$file" ]
    then
	if [ $ignore -eq 0 ];
	then
	    fail "$file: not an ordinary file"
	fi
	return
    fi

    file_type=`file -b $file`
    case "$file_type" in
	*"ELF "*)
            verbose "$file: ELF format - running script/program"
	    if test "x$prefix" != "x" ;
	    then
		report "$prefix:"
	    fi
	    run $script $script_opts $file
	    return
	    ;;
	"RPM "*)
            verbose "$file: RPM format."
	    ;;
	*" cpio "*)
            verbose "$file: CPIO format."
	    ;;
	*"tar "*)
	    verbose "$file: TAR archive."
	    ;;
	*"Zip archive"*)
	    verbose "$file: ZIP archive."
	    ;;
	*"ar archive"*)
	    verbose "$file: AR archive."
	    ;;
	*"bzip2 compressed data"*)
	    verbose "$file: contains bzip2 compressed data"
	    ;;
	*"gzip compressed data"*)
	    verbose "$file: contains gzip compressed data"
	    ;;
	*"lzip compressed data"*)
	    verbose "$file: contains lzip compressed data"
	    ;;
	*"XZ compressed data"*)
	    verbose "$file: contains xz compressed data"
	    ;;
	*"shell script"* | *"ASCII text"*)
	    if [ $ignore -eq 0 ];
	    then
		fail "$file: test/scripts cannot be scanned."
	    fi
	    return
	    ;;
	*"symbolic link"*)
	    if [ $ignore -eq 0 ];
	    then
		# FIXME: We ought to be able to follow symbolic links
		fail "$file: symbolic links are not followed."
	    fi
	    return
	    ;;
        *)
	    if [ $ignore -eq 0 ];
	    then
		fail "$file: Unsupported file type: $file_type"
	    fi
	    return
	    ;;
    esac
    
    # We now know that we will need a temporary directory
    # so create one, and create paths to the file and scripts.
    if test "x${file:0:1}" = "x/" ;
    then
	abs_file=$file
    else
	abs_file="$PWD/$file"
    fi
    
    if test "x${abs_prog:0:1}" != "x/" ;
    then
	abs_prog="$PWD/$abs_prog"
    fi

    if test "x${script:0:1}" = "x/" ;
    then
	abs_script=$script
    else
	abs_script="$PWD/$script"
    fi
    
    tmp_root=$tmpdir/delme.run.on.binary
    run mkdir -p "$tmp_root/$file"

    verbose "  Changing to directory: $tmp_root/$file"
    pushd "$tmp_root/$file" > /dev/null
    if [ $? != 0 ];
    then
	fail "Unable to change to temporary directory: $tmp_root/$file"
	return
    fi
			 
    # Run the file type switch again, although this time we do not need to
    # check for unrecognised types.  (But we do, just in case...)
    # Note since are transforming the file we re-invoke the run-on-binaries
    # script on the decoded contents.  This allows for archives that contain
    # other archives, and so on.  We normally pass the -i option to the
    # invoked script so that it will not complain about unrecognised files in
    # the decoded archive, although we do not do this when running in very
    # verbose mode.  We also pass an extended -t option to ensure that any
    # sub-archives are extracted into a unique directory tree.

    case "$file_type" in
	"RPM "*)
	    # The output redirect confuses the run function...
	    verbose "  Running: rpm2cpio $abs_file > delme.cpio"
	    rpm2cpio $abs_file > delme.cpio
	    if [ $? != 0 ];
	    then
		fail "$file: Unable to extract from rpm archive"
	    else
		# Save time - run cpio now.
		run cpio --quiet --extract --make-directories --file delme.cpio
		if [ $? != 0 ];
		then
		    fail "$file: Unable to extract files from cpio archive"
		fi
		run rm -f delme.cpio
	    fi
	    ;;

	*" cpio "*)
	    run cpio --quiet --extract --make-directories --file=$abs_file
	    if [ $? != 0 ];
	    then
		fail "$file: Unable to extract files from cpio archive"
	    fi
	    ;;

	*"tar "*)
	    run tar --extract --file=$abs_file
	    if [ $? != 0 ];
	    then
		fail "$file: Unable to extract files from tarball"
	    fi
	    ;;

	*"ar archive"*)
	    run ar x $abs_file
	    if [ $? != 0 ];
	    then
		fail "$file: Unable to extract files from ar archive"
	    fi
	    ;;

	*"Zip archive"*)
	    decompress $abs_file unzip "-q" $file
	    ;;
	*"bzip2 compressed data"*)
	    decompress $abs_file bzip2 "--quiet --decompress" $file
	    ;;
	*"gzip compressed data"*)
	    decompress $abs_file gzip "--quiet --decompress" $file
	    ;;
	*"lzip compressed data"*)
	    decompress $abs_file lzip "--quiet --decompress" $file
	    ;;
	*"XZ compressed data"*)
	    decompress $abs_file xz "--quiet --decompress" $file
	    ;;
	*)
	    ice "unhandled file type: $file_type"
	    ;;
     esac

    if [ $failed -eq 0 ];
    then
	# Now scan the file(s) created in the previous step.
	run find . -type f -execdir $abs_prog $prog_opts -t=$tmp_root/$file -p=$file $abs_script $script_opts {} +
    fi

    verbose "  Deleting temporary directory: $tmp_root"
    rm -fr $tmp_root

    verbose "  Return to previous directory"
    popd > /dev/null
}

# Invoke main
main ${1+"$@"}

 

接下来阅读什么

如何使用 Git 管理二进制大对象

阅读:第一部分:什么是 Git? 第二部分:Git 入门 第三部分:创建你的第一个 Git 仓库 第四部分:如何在 Git 中恢复旧版本文件 第五部分:3 个图形化…

标签

评论已关闭。

© . All rights reserved.