리눅스 바이너리가 뭐야?

青灯夜游
풀어 주다: 2023-04-18 14:06:29
원래의
2594명이 탐색했습니다.

리눅스에서 바이너리 파일은 파일을 가리키는데, 파일이 외부 장치에 바이너리 형식으로 저장되기 때문입니다. 바이너리 파일 인코딩은 가변 길이이며 유연한 활용률은 높지만 디코딩이 더 어렵고 다양한 바이너리 파일 디코딩 방법이 다릅니다. Linux의 실행 파일(스크립트, 텍스트 배치 파일은 포함되지 않음)은 바이너리 형식입니다.

리눅스 바이너리가 뭐야?

이 튜토리얼의 운영 환경: linux7.3 시스템, Dell G3 컴퓨터.

바이너리 파일이란?

일반 파일: 일반 액세스를 위한 파일입니다. ls -al로 표시되는 속성 중 첫 번째 속성은 [-rwxrwxrwx]와 같습니다. 또한, 파일 내용에 따라 크게 다음과 같이 나눌 수 있습니다.

  • 1. 일반 텍스트 파일(ASCII): 유닉스 시스템에서 가장 일반적인 파일 형식으로 일반 텍스트 파일이라고 합니다. 내용은 숫자, 문자 등 데이터를 직접 읽을 수 있기 때문입니다. 설정 파일은 거의 항상 이 파일 형식에 속합니다. 예를 들어, "cat ~/.bashrc" 명령을 사용하면 파일 내용을 볼 수 있습니다(cat는 파일 내용을 읽습니다).

  • 2. 바이너리 파일: 시스템은 실제로 바이너리 파일만 인식하고 실행할 수 있습니다. Linux에서 실행 가능한 파일(스크립트, 텍스트 배치 파일은 포함되지 않음)은 이 형식을 따릅니다. 예를 들어, cat 명령은 바이너리 파일입니다.

    광의의 바이너리 파일은 파일이 바이너리 형식으로 외부 장치에 저장되어 있는 것을 따서 명명된 파일을 의미합니다. 좁은 의미의 바이너리 파일은 텍스트 파일이 아닌 파일을 말합니다. 텍스트 파일은 여러 줄의 문자로 구성된 컴퓨터 파일입니다. 텍스트 파일은 컴퓨터 시스템에 존재하며 파일 끝 표시는 일반적으로 텍스트 파일의 마지막 줄에 표시됩니다. 텍스트 파일의 인코딩은 고정 길이 문자를 기반으로 하며 디코딩이 상대적으로 쉽습니다. 바이너리 파일의 인코딩은 가변 길이이며 유연한 활용률은 높지만 디코딩이 더 어렵고 바이너리 파일마다 디코딩이 다릅니다. 행동 양식.

  • 3. 데이터 형식의 파일(데이터): 일부 프로그램은 작동 중에 특정 형식의 파일을 읽습니다. 특정 형식의 파일을 데이터 파일(데이터 파일)이라고 할 수 있습니다. 예를 들어, 사용자가 로그인하면 Linux는 로그인 데이터를 /var/log/wtmp 파일에 기록합니다. 이 파일은 마지막 명령을 통해 읽을 수 있는 데이터 파일입니다. 그러나 cat을 사용하면 Linux 시스템은 잘못된 문자를 읽습니다. 특별한 형식의 파일이기 때문입니다.

Linux에서 바이너리 파일을 분석하는 10가지 방법

"세상에는 10가지 종류의 사람이 있습니다. 바이너리를 이해하는 사람과 바이너리를 이해하지 못하는 사람입니다.

우리는 매일 바이너리 파일을 다룹니다." , 그러나 우리는 바이너리에 대해 거의 알지 못합니다. 바이너리란 명령줄 도구부터 완전한 애플리케이션까지 매일 실행하는 실행 파일을 의미합니다.

Linux는 바이너리 파일을 쉽게 분석할 수 있는 풍부한 도구 세트를 제공합니다. 직무에 관계없이 Linux에서 작업하는 경우 이러한 도구의 기본 사항을 알면 시스템을 더 잘 이해하는 데 도움이 됩니다.

이 기사에서는 가장 널리 사용되는 Linux 도구 및 명령 중 일부를 소개하며, 대부분은 Linux 배포판의 일부입니다. 찾을 수 없는 경우 언제든지 패키지 관리자를 사용하여 설치하고 탐색할 수 있습니다. 기억하세요: 올바른 상황에 적합한 도구를 사용하는 방법을 배우려면 많은 인내와 연습이 필요합니다.

file

기능: 파일 형식을 결정하는 데 도움을 줍니다.

이것이 이진 분석의 출발점이 될 것입니다. 우리는 매일 파일을 다루며, 모든 파일이 실행 가능한 형식은 아닙니다. 또한 다양한 파일 형식이 있습니다. 시작하기 전에 분석하려는 파일 유형을 이해해야 합니다. 바이너리 파일, 라이브러리 파일, ASCII 텍스트 파일, 비디오 파일, 이미지 파일, PDF, 데이터 파일 등인가요?

file 명령은 작업 중인 파일 유형을 결정하는 데 도움이 됩니다.

rreee

ldd

它的作用:打印共享对象依赖关系。

如果你已经在一个可执行的二进制文件上使用了上面的 file 命令,你肯定会看到输出中的“ 动态链接(dynamically linked)”信息。它是什么意思呢?

在开发软件的时候,我们尽量不要重造轮子。有一组常见的任务是大多数软件程序需要的,比如打印输出或从标准输入/打开的文件中读取等。所有这些常见的任务都被抽象成一组通用的函数,然后每个人都可以使用,而不是写出自己的变体。这些常用的函数被放在一个叫 libcglibc 的库中。

如何找到可执行程序所依赖的库?这就是 ldd 命令的作用了。对动态链接的二进制文件运行该命令会显示出所有依赖库和它们的路径。

$ ldd /bin/ls
        linux-vdso.so.1 =>  (0x00007ffef5ba1000)
        libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fea9f854000)
        libcap.so.2 => /lib64/libcap.so.2 (0x00007fea9f64f000)
        libacl.so.1 => /lib64/libacl.so.1 (0x00007fea9f446000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fea9f079000)
        libpcre.so.1 => /lib64/libpcre.so.1 (0x00007fea9ee17000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007fea9ec13000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fea9fa7b000)
        libattr.so.1 => /lib64/libattr.so.1 (0x00007fea9ea0e000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fea9e7f2000)
$
로그인 후 복사

ltrace

它的作用:库调用跟踪器。

我们现在知道如何使用 ldd 命令找到一个可执行程序所依赖的库。然而,一个库可以包含数百个函数。在这几百个函数中,哪些是我们的二进制程序正在使用的实际函数?

ltrace 命令可以显示运行时从库中调用的所有函数。在下面的例子中,你可以看到被调用的函数名称,以及传递给该函数的参数。你也可以在输出的最右边看到这些函数返回的内容。

$ ltrace ls
__libc_start_main(0x4028c0, 1, 0x7ffd94023b88, 0x412950 <unfinished ...>
strrchr("ls", '/')                                                                  = nil
setlocale(LC_ALL, "")                                                               = "en_US.UTF-8"
bindtextdomain("coreutils", "/usr/share/locale")                                    = "/usr/share/locale"
textdomain("coreutils")                                                             = "coreutils"
__cxa_atexit(0x40a930, 0, 0, 0x736c6974756572)                                      = 0
isatty(1)                                                                           = 1
getenv("QUOTING_STYLE")                                                             = nil
getenv("COLUMNS")                                                                   = nil
ioctl(1, 21523, 0x7ffd94023a50)                                                     = 0
<< snip >>
fflush(0x7ff7baae61c0)                                                              = 0
fclose(0x7ff7baae61c0)                                                              = 0
+++ exited (status 0) +++
$
로그인 후 복사

hexdump

它的作用:以 ASCII、十进制、十六进制或八进制显示文件内容。

通常情况下,当你用一个应用程序打开一个文件,而它不知道如何处理该文件时,就会出现这种情况。尝试用 vim 打开一个可执行文件或视频文件,你屏幕上会看到的只是抛出的乱码。

hexdump 中打开未知文件,可以帮助你看到文件的具体内容。你也可以选择使用一些命令行选项来查看用 ASCII 表示的文件数据。这可能会帮助你了解到它是什么类型的文件。

$ hexdump -C /bin/ls | head
00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 3e 00 01 00 00 00  d4 42 40 00 00 00 00 00  |..>......B@.....|
00000020  40 00 00 00 00 00 00 00  f0 c3 01 00 00 00 00 00  |@...............|
00000030  00 00 00 00 40 00 38 00  09 00 40 00 1f 00 1e 00  |....@.8...@.....|
00000040  06 00 00 00 05 00 00 00  40 00 00 00 00 00 00 00  |........@.......|
00000050  40 00 40 00 00 00 00 00  40 00 40 00 00 00 00 00  |@.@.....@.@.....|
00000060  f8 01 00 00 00 00 00 00  f8 01 00 00 00 00 00 00  |................|
00000070  08 00 00 00 00 00 00 00  03 00 00 00 04 00 00 00  |................|
00000080  38 02 00 00 00 00 00 00  38 02 40 00 00 00 00 00  |8.......8.@.....|
00000090  38 02 40 00 00 00 00 00  1c 00 00 00 00 00 00 00  |8.@.............|
$
로그인 후 복사

strings

它的作用:打印文件中的可打印字符的字符串。

如果你只是在二进制中寻找可打印的字符,那么 hexdump 对于你的使用场景来说似乎有点矫枉过正,你可以使用 strings 命令。

在开发软件的时候,各种文本/ASCII 信息会被添加到其中,比如打印信息、调试信息、帮助信息、错误等。只要这些信息都存在于二进制文件中,就可以用 strings 命令将其转储到屏幕上。

$ strings /bin/ls
로그인 후 복사

readelf

它的作用:显示有关 ELF 文件的信息。

ELF( 可执行和可链接文件格式(Executable and Linkable File Format))是可执行文件或二进制文件的主流格式,不仅是 Linux 系统,也是各种 UNIX 系统的主流文件格式。如果你已经使用了像 file 命令这样的工具,它告诉你文件是 ELF 格式,那么下一步就是使用 readelf 命令和它的各种选项来进一步分析文件。

在使用 readelf 命令时,有一份实际的 ELF 规范的参考是非常有用的。你可以在这里找到该规范。

$ readelf -h /bin/ls
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x4042d4
  Start of program headers:          64 (bytes into file)
  Start of section headers:          115696 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         31
  Section header string table index: 30
$
로그인 후 복사

objdump

它的作用:从对象文件中显示信息。

二进制文件是通过你编写的源码创建的,这些源码会通过一个叫做编译器的工具进行编译。这个编译器会生成相对于源代码的机器语言指令,然后由 CPU 执行特定的任务。这些机器语言代码可以通过被称为汇编语言的助记词来解读。汇编语言是一组指令,它可以帮助你理解由程序所进行并最终在 CPU 上执行的操作。

objdump 实用程序读取二进制或可执行文件,并将汇编语言指令转储到屏幕上。汇编语言知识对于理解 objdump 命令的输出至关重要。

请记住:汇编语言是特定于体系结构的。

$ objdump -d /bin/ls | head

/bin/ls:     file format elf64-x86-64

Disassembly of section .init:

0000000000402150 <_init@@Base>:
  402150:       48 83 ec 08             sub    $0x8,%rsp
  402154:       48 8b 05 6d 8e 21 00    mov    0x218e6d(%rip),%rax        # 61afc8 <__gmon_start__>
  40215b:       48 85 c0                test   %rax,%rax
$
로그인 후 복사

strace

它的作用:跟踪系统调用和信号。

如果你用过前面提到的 ltrace,那就把 strace 想成是类似的。唯一的区别是,strace 工具不是追踪调用的库,而是追踪系统调用。系统调用是你与内核对接来完成工作的。

举个例子,如果你想把一些东西打印到屏幕上,你会使用标准库 libc 中的 printfputs 函数;但是,在底层,最终会有一个名为 write 的系统调用来实际把东西打印到屏幕上。

$ strace -f /bin/ls
execve("/bin/ls", ["/bin/ls"], [/* 17 vars */]) = 0
brk(NULL)                               = 0x686000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f967956a000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=40661, ...}) = 0
mmap(NULL, 40661, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f9679560000
close(3)                                = 0
<< snip >>
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9679569000
write(1, "R2  RH\n", 7R2  RH
)                 = 7
close(1)                                = 0
munmap(0x7f9679569000, 4096)            = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++
$
로그인 후 복사

nm

它的作用:列出对象文件中的符号。

如果你所使用的二进制文件没有被剥离,nm 命令将为你提供在编译过程中嵌入到二进制文件中的有价值的信息。nm 可以帮助你从二进制文件中识别变量和函数。你可以想象一下,如果你无法访问二进制文件的源代码时,这将是多么有用。

为了展示 nm,我们快速编写了一个小程序,用 -g 选项编译,我们会看到这个二进制文件没有被剥离。

$ cat hello.c
#include <stdio.h>

int main() {
    printf("Hello world!");
    return 0;
}
$
$ gcc -g hello.c -o hello
$
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=3de46c8efb98bce4ad525d3328121568ba3d8a5d, not stripped
$
$ ./hello
Hello world!$
$


$ nm hello | tail
0000000000600e20 d __JCR_END__
0000000000600e20 d __JCR_LIST__
00000000004005b0 T __libc_csu_fini
0000000000400540 T __libc_csu_init
                 U __libc_start_main@@GLIBC_2.2.5
000000000040051d T main
                 U printf@@GLIBC_2.2.5
0000000000400490 t register_tm_clones
0000000000400430 T _start
0000000000601030 D __TMC_END__
$
로그인 후 복사

gdb

它的作用:GNU 调试器。

好吧,不是所有的二进制文件中的东西都可以进行静态分析。我们确实执行了一些运行二进制文件(进行分析)的命令,比如 ltracestrace;然而,软件由各种条件组成,这些条件可能会导致执行不同的替代路径。

分析这些路径的唯一方法是在运行时环境,在任何给定的位置停止或暂停程序,并能够分析信息,然后再往下执行。

这就是调试器的作用,在 Linux 上,gdb 就是调试器的事实标准。它可以帮助你加载程序,在特定的地方设置断点,分析内存和 CPU 的寄存器,以及更多的功能。它是对上面提到的其他工具的补充,可以让你做更多的运行时分析。

有一点需要注意的是,一旦你使用 gdb 加载一个程序,你会看到它自己的 (gdb) 提示符。所有进一步的命令都将在这个 gdb 命令提示符中运行,直到你退出。

我们将使用我们之前编译的 hello 程序,使用 gdb 来看看它的工作原理。

$ gdb -q ./hello
Reading symbols from /home/flash/hello...done.
(gdb) break main
Breakpoint 1 at 0x400521: file hello.c, line 4.
(gdb) info break
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400521 in main at hello.c:4
(gdb) run
Starting program: /home/flash/./hello

Breakpoint 1, main () at hello.c:4
4           printf("Hello world!");
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7_6.6.x86_64
(gdb) bt
#0  main () at hello.c:4
(gdb) c
Continuing.
Hello world![Inferior 1 (process 29620) exited normally]
(gdb) q
$
로그인 후 복사

相关推荐:《Linux视频教程

위 내용은 리눅스 바이너리가 뭐야?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿