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ị:
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.55
: Đây là chỉ thịpush %rbp
, lưu con trỏ base pointer vào stack.48 89 e5
: Đây là chỉ thịmov %rsp,%rbp
, đặt base pointer thành con trỏ stack hiện tại.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àmputs
.e8 85 fe ff ff
: Đây là chỉ thịcallq f20 <puts@plt>
, gọi hàmputs
để in thông báo "Hello, World!".b8 00 00 00 00
: Đây là chỉ thịmov $0x0,%eax
, đặt giá trị trả về của hàmmain
thành 0.5d
: Đây là chỉ thịpop %rbp
, khôi phục base pointer từ stack.c3
: Đây là chỉ thịretq
, trả về từ hàmmain
.
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.