Lệnh objdump trong Linux

Giới thiệu

Trong lab này, bạn sẽ học cách sử dụng lệnh objdump của Linux, đây là một công cụ mạnh mẽ để phân tích nội dung của các file object. Bạn sẽ bắt đầu bằng cách hiểu mục đích của lệnh objdump, sau đó khám phá cú pháp và các tùy chọn cơ bản của nó, và cuối cùng là phân tích đầu ra của objdump trên một chương trình C đơn giản. Lab này bao gồm các kỹ năng thiết yếu trong lĩnh vực scripting và lập trình, và kiến thức thu được có thể được áp dụng vào nhiều tác vụ phát triển và gỡ lỗi phần mềm.

Hiểu Mục Đích của Lệnh objdump

Trong bước này, bạn sẽ tìm hiểu về mục đích của lệnh objdump trong Linux. Lệnh objdump là một công cụ mạnh mẽ được sử dụng để phân tích nội dung của các file object, là các file nhị phân chứa mã máy và các thông tin khác.

Lệnh objdump có thể được sử dụng để dịch ngược mã máy trong một file object, có nghĩa là nó có thể dịch các chỉ thị nhị phân sang định dạng mà con người có thể đọc được. Điều này có thể hữu ích để hiểu cách một chương trình hoạt động, gỡ lỗi sự cố hoặc thậm chí là kỹ thuật đảo ngược phần mềm.

Hãy bắt đầu bằng cách tạo một chương trình C đơn giản và sử dụng objdump để phân tích nội dung của nó.

Đầu tiên, tạo một file mới có tên hello.c trong thư mục ~/project với nội dung sau:

#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

Tiếp theo, biên dịch chương trình bằng trình biên dịch gcc:

cd ~/project
gcc -o hello hello.c

Bây giờ, hãy sử dụng lệnh objdump để phân tích nội dung của file thực thi hello:

objdump -d hello

Ví dụ đầu ra:

hello:     file format elf64-x86-64

Disassembly of section .init:

0000000000001000 <_init>:
    1000:   f3 0f 1e fa             endbr64
    1004:   48 83 ec 08             sub    $0x8,%rsp
    1008:   48 8b 05 d9 2f 00 00    mov    0x2fd9(%rip),%rax        ## 3fe8 <__gmon_start__>
    100f:   48 85 c0                test   %rax,%rax
    1012:   74 02                   je     1016 <_init+0x16>
    1014:   ff d0                   callq  *%rax
    1016:   48 83 c4 08             add    $0x8,%rsp
    101a:   c3                      retq

Lệnh objdump dịch ngược mã máy trong file thực thi hello, hiển thị các chỉ thị cấp thấp mà bộ xử lý sẽ thực thi khi chương trình chạy. Điều này có thể hữu ích để hiểu cách chương trình hoạt động và để gỡ lỗi các vấn đề.

Trong bước tiếp theo, bạn sẽ khám phá cú pháp và các tùy chọn cơ bản của lệnh objdump.

Khám Phá Cú Pháp và Các Tùy Chọn Cơ Bản của objdump

Trong bước này, bạn sẽ khám phá cú pháp và các tùy chọn cơ bản của lệnh objdump. Lệnh objdump có nhiều tùy chọn cho phép bạn tùy chỉnh đầu ra và trích xuất thông tin cụ thể từ file object.

Hãy bắt đầu bằng cách xem xét cú pháp cơ bản của lệnh objdump:

objdump [options] file

Dưới đây là một số tùy chọn phổ biến nhất cho lệnh objdump:

  • -d hoặc --disassemble: Dịch ngược mã thực thi.
  • -S hoặc --source: Trộn mã nguồn với quá trình dịch ngược.
  • -t hoặc --syms: Hiển thị nội dung của bảng biểu tượng.
  • -x hoặc --all-headers: Hiển thị tất cả thông tin header có sẵn.
  • -h hoặc --section-headers: Hiển thị nội dung của section header.

Hãy thử một số tùy chọn này với file thực thi hello mà chúng ta đã tạo ở bước trước:

## Display the disassembly with source code
objdump -dS hello

## Display the symbol table
objdump -t hello

## Display all available header information
objdump -x hello

Ví dụ đầu ra:

hello:     file format elf64-x86-64

Disassembly of section .text:

0000000000001000 <_init>:
    1000:   f3 0f 1e fa             endbr64
    1004:   48 83 ec 08             sub    $0x8,%rsp
    1008:   48 8b 05 d9 2f 00 00    mov    0x2fd9(%rip),%rax        ## 3fe8 <__gmon_start__>
    100f:   48 85 c0                test   %rax,%rax
    1012:   74 02                   je     1016 <_init+0x16>
    1014:   ff d0                   callq  *%rax
    1016:   48 83 c4 08             add    $0x8,%rsp
    101a:   c3                      retq

0000000000001020 <__libc_csu_init>:
    1020:   f3 0f 1e fa             endbr64
    1024:   41 57                   push   %r15
    1026:   4c 8d 3d 93 2c 00 00    lea    0x2c93(%rip),%r15        ## 3cc0 <__frame_dummy_init_array_entry>
    102d:   41 56                   push   %r14
    102f:   49 89 e6                mov    %rsp,%r14
    1032:   41 55                   push   %r13
    1034:   41 54                   push   %r12
    1036:   4c 8d 25 83 2c 00 00    lea    0x2c83(%rip),%r12        ## 3cc0 <__frame_dummy_init_array_entry>
    103d:   55                      push   %rbp
    103e:   48 8d 2d 83 2c 00 00    lea    0x2c83(%rip),%rbp        ## 3cc8 <__do_global_dtors_aux_fini_array_entry>
    1045:   53                      push   %rbx
    1046:   4c 29 e5                sub    %r12,%rbp
    1049:   48 83 ec 08             sub    $0x8,%rsp
    104d:   e8 ae fe ff ff          callq  f00 <_init>
    1052:   48 c1 fd 03             sar    $0x3,%rbp
    1056:   74 1f                   je     1077 <__libc_csu_init+0x57>
    1058:   31 db                   xor    %ebx,%ebx
    105a:   0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
    105f:   4c 89 f2                mov    %r14,%rdx
    1062:   4c 89 ee                mov    %r13,%rsi
    1065:   44 89 e7                mov    %r12d,%edi
    1068:   41 ff 14 df             callq  *(%r15,%rbx,8)
    106c:   48 83 c3 01             add    $0x1,%rbx
    1070:   48 39 dd                cmp    %rbx,%rbp
    1073:   75 ea                   jne    105f <__libc_csu_init+0x3f>
    1075:   48 83 c4 08             add    $0x8,%rsp
    1079:   5b                      pop    %rbx
    107a:   5d                      pop    %rbp
    107b:   41 5c                   pop    %r12
    107d:   41 5d                   pop    %r13
    107f:   41 5e                   pop    %r14
    1081:   41 5f                   pop    %r15
    1083:   c3                      retq

0000000000001084 <__libc_csu_fini>:
    1084:   f3 0f 1e fa             endbr64
    1088:   c3                      retq

0000000000001089 <main>:
    1089:   f3 0f 1e fa             endbr64
    108d:   55                      push   %rbp
    108e:   48 89 e5                mov    %rsp,%rbp
    1091:   bf 00 00 00 00          mov    $0x0,%edi
    1096:   e8 85 fe ff ff          callq  f20 <puts@plt>
    109b:   b8 00 00 00 00          mov    $0x0,%eax
    10a0:   5d                      pop    %rbp
    10a1:   c3                      retq
    10a2:   66 2e 0f 1f 84 00 00 00 00 00 nopw   %cs:0x0(%rax,%rax,1)
    10ac:   0f 1f 40 00             nopl   0x0(%rax)

Disassembly of section .fini:

00000000000010b0 <_fini>:
    10b0:   f3 0f 1e fa             endbr64
    10b4:   48 83 ec 08             sub    $0x8,%rsp
    10b8:   48 83 c4 08             add    $0x8,%rsp
    10bc:   c3                      retq

Đầu ra hiển thị quá trình dịch ngược của file thực thi hello, bao gồm mã nguồn xen kẽ với các chỉ thị máy. Điều này có thể rất hữu ích để hiểu cách chương trình hoạt động ở cấp độ thấp.

Trong bước tiếp theo, bạn sẽ phân tích chi tiết hơn đầu ra của objdump trên chương trình hello.

Phân Tích Đầu Ra của objdump trên một Chương Trình C Đơn Giản

Trong bước cuối cùng này, bạn sẽ phân tích chi tiết hơn đầu ra của lệnh objdump, tập trung vào chương trình hello mà chúng ta đã tạo trước đó.

Hãy bắt đầu bằng cách xem xét kỹ hơn đầu ra của quá trình dịch ngược:

objdump -d hello

Ví dụ đầu ra:

hello:     file format elf64-x86-64

Disassembly of section .text:

0000000000001089 <main>:
    1089:   f3 0f 1e fa             endbr64
    108d:   55                      push   %rbp
    108e:   48 89 e5                mov    %rsp,%rbp
    1091:   bf 00 00 00 00          mov    $0x0,%edi
    1096:   e8 85 fe ff ff          callq  f20 <puts@plt>
    109b:   b8 00 00 00 00          mov    $0x0,%eax
    10a0:   5d                      pop    %rbp
    10a1:   c3                      retq

Đầu ra của quá trình dịch ngược hiển thị các chỉ thị máy tạo nên hàm main của chương trình hello. Hãy phân tích các chỉ thị:

  1. f3 0f 1e fa: Đây là chỉ thị endbr64, một tính năng bảo mật để ngăn chặn một số loại tấn công.
  2. 55: Đây là chỉ thị push %rbp, lưu con trỏ base pointer vào stack.
  3. 48 89 e5: Đây là chỉ thị mov %rsp,%rbp, đặt base pointer thành con trỏ stack hiện tại.
  4. bf 00 00 00 00: Đây là chỉ thị mov $0x0,%edi, đặt đối số đầu tiên (file descriptor) thành 0 cho lệnh gọi hàm puts.
  5. e8 85 fe ff ff: Đây là chỉ thị callq f20 <puts@plt>, gọi hàm puts để in thông báo "Hello, World!".
  6. b8 00 00 00 00: Đây là chỉ thị mov $0x0,%eax, đặt giá trị trả về của hàm main thành 0.
  7. 5d: Đây là chỉ thị pop %rbp, khôi phục base pointer từ stack.
  8. c3: Đây là chỉ thị retq, trả về từ hàm main.

Bằng cách hiểu đầu ra của quá trình dịch ngược, bạn có thể hiểu sâu hơn về cách chương trình hello hoạt động ở cấp độ thấp. Điều này có thể đặc biệt hữu ích để gỡ lỗi các vấn đề hoặc kỹ thuật đảo ngược phần mềm.

Tóm tắt

Trong lab này, ban đầu bạn đã tìm hiểu về mục đích của lệnh objdump trong Linux, một công cụ mạnh mẽ được sử dụng để phân tích nội dung của các file object. Bạn đã tạo một chương trình C đơn giản, biên dịch nó và sau đó sử dụng objdump để dịch ngược mã máy trong file thực thi, cho phép bạn hiểu cách chương trình hoạt động ở cấp độ thấp. Tiếp theo, bạn đã khám phá cú pháp và các tùy chọn cơ bản của lệnh objdump, có thể được sử dụng để trích xuất các loại thông tin khác nhau từ các file object, chẳng hạn như bảng biểu tượng, thông tin định vị lại và hơn thế nữa. Đến cuối lab, bạn đã hiểu rõ về cách sử dụng objdump để phân tích nội dung của các file thực thi trên một hệ thống Linux.

400+ câu lệnh phổ biến trong Linux