Reverse

Binary 102: Linux 32-bit Assembly

category
Reverse
date
Feb 26, 2021
slug
binary-102-linux-32-bit-assembly
author
status
Public
tags
binary analysis
reverse
summary
Cơ bản về x86 Assembly trên Linux, cấu trúc tệp ELF32.
type
Post
thumbnail
updatedAt
Mar 4, 2023 01:36 AM
Phần này sẽ giới thiệu cho bạn đọc kiến thức cơ bản về x86 Assembly trên Linux, chúng ta sẽ lần lượt đi qua các chủ đề như: Phân biệt giữa cú pháp Intel và AT&T, Registers, Stack, System Call, Các Instruction thường gặp,.v.v.. rất nhàm chán phải không? nhưng nó cũng rất quan trọng. Ngoài ra chương này cũng giới thiệu về cấu trúc tệp tin ELF32, đây là một cấu trúc tệp thực thi chiếm phần lớn trên Linux, nó như PE trên Windows vậy.

1. Cú pháp Intel và AT&T

Khi làm việc với Assembly thì chúng ta bắt gặp nhiều nhất là 2 dạng cú pháp: IntelAT&T. Có nhiều công cụ trên Linux khi thực hiện disassembly một ELF file thì mặc định nó sẽ cho đầu ra theo cú pháp AT&T. Tuy nhiên chúng ta sẽ học và làm việc chủ yếu với cú pháp của Intel 🤤
Để minh họa, tôi sử dụng objdump để disassembly một mẫu theo cả 2 cú pháp như sau:
Cú pháp AT&T:
$ objdump -d ch02-helloworld ch02-helloworld: file format elf32-i386 Disassembly of section .text: 08048060 <_start>: 8048060: 31 c0 xor %eax,%eax 8048062: 31 db xor %ebx,%ebx 8048064: 31 c9 xor %ecx,%ecx 8048066: 31 d2 xor %edx,%edx 8048068: b0 04 mov $0x4,%al 804806a: fe c3 inc %bl 804806c: 68 64 21 0a 00 push $0xa2164 8048071: 68 57 6f 72 6c push $0x6c726f57 8048076: 68 6c 6f 2c 20 push $0x202c6f6c 804807b: 68 48 65 6c 00 push $0x6c6548 8048080: 89 e1 mov %esp,%ecx 8048082: b2 0f mov $0xf,%dl 8048084: cd 80 int $0x80 8048086: 31 c0 xor %eax,%eax 8048088: 31 db xor %ebx,%ebx 804808a: b0 01 mov $0x1,%al 804808c: cd 80 int $0x80
Cú pháp Intel:
$ objdump -d -M intel ch02-helloworld ch02-helloworld: file format elf32-i386 Disassembly of section .text: 08048060 <_start>: 8048060: 31 c0 xor eax,eax 8048062: 31 db xor ebx,ebx 8048064: 31 c9 xor ecx,ecx 8048066: 31 d2 xor edx,edx 8048068: b0 04 mov al,0x4 804806a: fe c3 inc bl 804806c: 68 64 21 0a 00 push 0xa2164 8048071: 68 57 6f 72 6c push 0x6c726f57 8048076: 68 6c 6f 2c 20 push 0x202c6f6c 804807b: 68 48 65 6c 00 push 0x6c6548 8048080: 89 e1 mov ecx,esp 8048082: b2 0f mov dl,0xf 8048084: cd 80 int 0x80 8048086: 31 c0 xor eax,eax 8048088: 31 db xor ebx,ebx 804808a: b0 01 mov al,0x1 804808c: cd 80 int 0x80
Đầu ra của objdump cơ bản có 4 cột, theo chiều từ trái sang phải ta có:
  • Cột 1: Địa chỉ các lệnh trong Assembly
  • Cột 2: Opcode của lệnh và toán hạng
  • Cột 3: Mã lệnh Assembly
  • Cột 4: Các toán hạng: nguồn, đích
Như vậy sự khác biệt rõ rệt nhất giữa cú pháp IntelAT&T nằm ở cột thứ 4. Ta đúc rút ra được công thức như sau:
Cú pháp AT&T:
<instruction> <Toán Hạng Nguồn>,<Toán Hạng Đích>
Cú pháp Intel:
<instruction> <Toán Hạng Đích>,<Toán Hạng Nguồn>
Bảng dưới đây tổng hợp những sự khác biệt chính giữa cú pháp Intel và AT&T. Có tham khảo tại: http://staffwww.fullcoll.edu/aclifton/courses/cs241/syntax.html
Intel vs AT&T syntax
Name
Intel
AT&T
Untagged add
Tagged with operand sizes: addq
eax, ebx, etc.
%eax%ebx, etc.
0x100
$0x100
[eax]
(%eax)
[base + reg + reg * scale + displacement]
displacement(reg, reg, scale)

2. Các thanh ghi trong x86 Assembly

2.1. CPU và Endianness

Trước khi tìm hiểu về các thanh ghi trong x86 Assembly, hãy tìm hiểu một chút về Bộ vi xử lý mà máy tính chúng ta đang sử dụng. Mở Terminal và chạy một số lệnh bên dưới như sau:
$ lscpu Architecture: i686 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian ... Vendor ID: GenuineIntel ... Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss nx pdpe1gb rdtscp lm constant_tsc arch_perfmon xtopology tsc_reliable nonstop_tsc cpuid pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch cpuid_fault pti ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 avx2 smep bmi2 invpcid rdseed adx smap clflushopt xsaveopt xsavec xgetbv1 xsaves arat md_clear flush_l1d arch_capabilities
Kết quả của lệnh này cung cấp một số thông tin quan trọng như:
  • Architecture: Kiến trúc Bộ VXL
  • CPU op-mode(s): Chế độ hoạt động của VXL
  • Byte Order: Đây là thông tin quan trọng, cho biết loại Endianness, nó sẽ quy định kiểu cách mà nó lưu dữ liệu trên bộ nhớ (Register, Stack)
  • Flags: Cho biết những loại thanh ghi mở rộng mà CPU hỗ trợ
Trên các bộ Vi xử lý của Intel sử dụng quy ước Little Endian. Để dễ hình dung và so sánh giữa Big-EndianLittle-Endian hãy xem hình bên dưới đây: Giả sử ta cần biểu diễn một số nguyên 32-bits có giá trị là 0x0A0B0C0D
Để giải thích ngắn gọn và dễ hiểu về Big-EndianLittle-Endian, ta thống nhất một số quy tắc như sau. Vẫn lấy số nguyên 32-bits 0x0A0B0C0D làm ví dụ:
  1. Byte có trọng số lớn nhất sẽ nằm ngoài cùng bên trái ⇒ 0x0A
  1. Byte có trọng số nhỏ nhất sẽ nằm ngoài cùng bên phải ⇒ 0x0D
  1. Theo chiều từ trái sang phải thì các byte lần lượt có trọng số giảm dần: 0x0A, 0x0B, 0x0C, 0x0D
  1. Địa chỉ trên bộ nhớ sẽ được đánh tăng dần theo chiều từ trái sang phải và từ trên xuống dưới: a, a+1, a+2, a+3
Ta có “công thức” như sau khi phân biệt giữa Big-EndianLittle-Endian:
💡
Big-Endian quy định BYTE có trọng số lớn nhất được lưu ở ô nhớ có địa chỉ nhỏ nhất còn BYTE có trọng số nhỏ nhất được lưu ở ô nhớ có địa chỉ lớn nhất. Little-Endian quy định BYTE có trọng số lớn nhất được lưu ở ô nhớ có địa nhỉ lớn nhất còn BYTE có trọng số nhỏ nhất được lưu ở ô nhớ có địa chỉ nhỏ nhất.
Để cho dễ hiểu thì ta chỉ cần nhớ rằng: Với Big-Endian dữ liệu được biểu diễn theo cách thông thường từ trái qua phải, không thay đổi. Còn với Little-Endian thì dữ liệu sẽ bị đảo ngược lại theo các byte. Ví dụ để biểu diễn 0x1A2B3C4D thì: Với Big-Endian sẽ giữ nguyên 0x1A2B3C4D còn với Little-Endian sẽ là: 0x4D3C2B1A
Trong thực tế hay gặp dữ liệu lưu trên ổ cứng (Hard Disk) hay dữ liệu hiển thị trong các công cụ phân tích gói tin (Wireshark/TCPDump),.v.v ở dạng Big-Endian. Còn dữ liệu khi được nạp lên bộ nhớ (RAM) thì sẽ ở dạng Little-Endian.

2.2. Các thanh ghi thường gặp

Show me:
Các bộ vi xử lý hiện đại ngày nay đã phát triển thêm và gia tăng về số lượng các thanh ghi, phục vụ các tác vụ như: tính toán số học dấu phẩy động, hay thanh ghi thực hiện một chức năng riêng nào đó,.v.v.. Tuy nhiên, khi làm việc với Assembly 32-bits thì chỉ cần chú ý đến một số thanh ghi phổ biến sau đây:
Các thanh ghi chung: Hay còn được biết đến nhóm các thanh ghi đa năng, được CPU sử dụng như bộ nhớ siêu tốc trong việc tính toán, dùng làm biến tạm, tham số,.v.v..
  • EAX: Đa mục đích, thường lưu giá trị trả về của một hàm. Chia nhỏ được thành: AX (16-bits), AH (8-bits), AL (8-bits)
  • EBX: Đa mục đích, thường được sử dụng như một con trỏ tới dữ liệu (nằm trong thanh ghi phân đoạn - DS, khi ở chế độ phân đoạn). Chia nhỏ được thành: BX, BH, BL.
  • ECX: Dùng trong các vòng lặp, được dùng như biến đếm. Chia nhỏ được thành: CX, CH, CL.
  • EDX: Dùng để lưu dữ liệu, cho các hoạt động I/O và phép toán số học. Chia nhỏ được thành: DX, DH, DL.
  • ESI: Dùng trong các thao tác với chuỗi, thường trỏ đến chuỗi nguồn. Chia nhỏ được thành: SI và SIL
  • EDI: Tương tự như ESI nhưng trỏ đến chuỗi đích. Chia nhỏ được thành: DI và DIL
  • ESP: Thanh ghi con trỏ ngăn xếp, luôn trỏ tới đỉnh hiện thời của ngăn xếp. Dùng khi có các thao tác trên Stack. Chia nhỏ được thành: SP và SPL
  • EBP: Thanh ghi con trỏ cơ sở (hay Frame Pointer). Khi một Stack Frame được cấp, có thể dựa vào ESP để xác định vị trí của: Return address, Parameter, Local variable,.v.v.. Chia nhỏ được thành: BP và BPL
Các thanh ghi đoạn: Gồm: CS, SS, DS, ES, FS và GS. Việc dùng các thanh ghi này phụ thuộc vào mô hình bộ nhớ của hệ điều hành, chỉ quan tâm nó dưới góc độ người lập trình Assembly. Ngày nay các Hệ điều hành phổ biến (Windows, Linux, FreeBSD,.v.v..) đều đã chuyển sang sử dụng chế độ phân trang (Flat mode) thay cho phân đoạn. Tuy nhiên, các thanh ghi này vẫn được sử dụng trong một số trường hợp nhất định.
Thanh ghi cờ - EFLAGS: Thanh ghi này rộng 32-bits, với mỗi vị trí bit được đánh tương ứng với một cờ hiệu. Các cờ hiệu này sẽ có 2 trạng thái là: 1 (cờ được bật) và 0 (cờ bị xóa). Các cờ này bị thay đổi khi gặp các lệnh/thao tác tính toán. Chúng ta không cần thiết phải nhớ hết các cờ, mà chỉ cần lưu ý một số cờ sau:
  • Zero Flag (ZF - Cờ không - Bit thứ 6): Được bật khi kết quả phép toán bằng 0 hoặc kết quả so sánh bằng nhau.
  • Carry Flag (CF - Cờ nhớ - Bit thứ 0): Được bật khi có mượn hoặc nhớ bit MSB
  • Parity Flag (PF - Cờ chẵn lẻ - Bit thứ 2): Được bật khi tổng số bit 1 trong kết quả là chẵn, nếu là lẻ thì cờ bị xóa
  • Sign Flag (SF - Cờ dấu - Bit thứ 7): Cờ này được bật khi bit MSB của kết quả bằng 1 tức đây là một kết quả âm
  • Overflow Flag (OF - Cờ tràn - Bit thứ 11): Được bật khi thực hiện phép tính với hai số cùng dấu mà kết quả là số có dấu
  • Direction Flag (DF - Cờ hướng - Bit thứ 10): Xác định hướng của thao tác chuỗi. Khi được bật hướng từ địa chỉ cao → thấp. Khi cờ đc xóa thì hướng từ địa chỉ thấp → cao.
  • Trap Flag (TF - Cờ bẫy - Bit thứ 8): Được bật để sử dụng chế độ gỡ lỗi, CPU sẽ chỉ thực hiện một lệnh tại một thời điểm
Thanh ghi con trỏ lệnh (IP/EIP): Luôn trỏ đến địa chỉ của lệnh kế tiếp sẽ được thực thi vì vậy nó quan trọng trong khai thác. Khi kiểm soát được thanh ghi này, có thể trỏ đến shellcode.
Bảng tổng hợp các thanh ghi: Các thanh ghi 64-bits sẽ được trình bày ở bài sau.
Bảng tổng hợp các cờ và vị trí:

3. Các lệnh thường gặp trong x86 Assembly

Các lệnh liên quan đến Stack:
  • PUSH: Dùng để đẩy (thêm/cất) dữ liệu vào đỉnh ngăn xếp. Khi dữ liệu được thêm vào ngăn xếp thì đồng thời thanh ghi ESP cũng bị giảm đi.
  • POP: Dùng để lấy dữ liệu ra từ đỉnh ngăn xếp. Khi dữ liệu được lấy ra khỏi ngăn xếp thì đồng thời thanh ghi ESP cũng sẽ được tăng lên.
Các lệnh Logic: Là các phép toán thao tác Bits:
  • AND: Kết quả của AND bằng 1 nếu như hai bit là 1, ngược lại bằng 0
  • OR: Kết quả của phép OR bằng 0 nếu như hai bit là 0, ngược lại bằng 1
  • NOT: Phép phủ định, cho kết quả ngược lại
  • XOR: Hai bit giống nhau thì bằng 0, khác nhau thì bằng 1
Các lệnh Cộng-Trừ-Nhân-Chia:
Lệnh ADD: Toán hạng đích = Toán hạng đích + Toán hạng nguồn (Intel Syntax)
Lệnh SUB: Toán hạng đích = Toán hạng đích - Toán hạng nguồn (Intel Syntax)
Lệnh MUL: Toán hạng đích = Toán hạng đích * Toán hạng nguồn. Toán hạng đích tùy thuộc vào kích thước của toán hạng nguồn, thường toán hạng đich sẽ tương ứng với: EAX, AX, AL
Lệnh DIV: Toán hạng đích = Toán hạng đích / Toán hạng nguồn. Tương tự như MUL nhưng là phép chia.
Các lệnh tăng giảm giá trị:
Lệnh INCDEC: Tương ứng với: Toán hạng đích = Toán hạng đích + 1 và Toán hạng đích = Toán hạng đích - 1 (Intel Syntax)
Lệnh khác:
Lệnh MOV: Chuyển dữ liệu giữa: thanh ghi với thanh ghi, thanh ghi với ô nhớ,.v.v..
Lệnh LEA: Tương tự MOV nhưng toán hạng đích (Intel Syntax) thường là các thanh ghi còn toán hạng nguồn là địa chỉ ô nhớ.
Lệnh XCHG: Hoán vị nội dung 2 toán hạng: Swap(Toán hạng đích, Toán hạng nguồn)

4. x86 Assembly System Calls trên Linux

Phần này quan trọng và thú vị nhé các bạn mình 🤤. Thông thường các chương trình độc hại hay shellcode thường thực hiện các cuộc gọi hệ thống một cách trực tiếp, mà không sử dụng các hàm có trong thư viện. Nếu nắm chắc được phần này thì ở các chương sau khi phân tích các chương trình độc hại như: Bind Shell, Reverse Shell, Polymorphism Shell sẽ không gặp khó khăn nhiều.

4.1. System call number, Ngắt và Man Page

Trong x86 Assembly, shellcode thường thực hiện một System call thông qua việc gọi NGẮT (INT 0x80). Các ngắt này sẽ dựa vào các System Call Number được định nghĩa trong Header file của thư viện hệ thống để tìm đến hàm cần gọi. Có nhiều header file trong hệ thống nhưng trong phần này và các phần tiếp theo khi phân tích Shellcode 32-bits sẽ chủ yếu tra cứu các API trong tệp: /usr/include/i386-linux-gnu/asm/unistd_32.h hoặc /‌arch/‌x86/‌include/‌generated/‌uapi/‌asm/‌unistd_32.h. Ví dụ dưới đây cho biết hàm exit có System call number là 1:
$ cat /usr/include/i386-linux-gnu/asm/unistd_32.h #ifndef _ASM_X86_UNISTD_32_H #define _ASM_X86_UNISTD_32_H 1 #define __NR_restart_syscall 0 #define __NR_exit 1 #define __NR_fork 2 ...
Khi đã biết System Call Number của một hàm thì cần phải biết được hàm đó sử dụng như nào, truyền các tham số ra làm sao. Sử dụng Man Page trên Linux để tra cứu. Ví dụ cách dùng exit:
$ man 2 exit
Kết quả sẽ được như sau cho biết hàm này nhận vào một số nguyên, báo hiệu mã trả về khi kết thúc một chương trình:
EXIT(3) Linux Programmer's Manual EXIT(3) NAME exit - cause normal process termination SYNOPSIS #include <stdlib.h> void exit(int status); ...
Các Parameter truyền vào khi gọi hàm tuân theo quy tắc sau đây:
Ta có “công thức” cần nhớ:
💡
- x86 Assembly thực hiện System Call thông qua lệnh: INT 0x80 gọi là Ngắt 0x80 - Thanh ghi EAX/AX sẽ lưu System Call NumberResult của System Call. - Các tham số theo thứ tự sau: EBX, ECX, EDX, ESI, EDI, EBP - Tra cứu các System Call Number tại: unistd_32.h - Tra cứu các API bằng Man Page của Linux

4.2. Phân tích một chương trình x86 Assembly đơn giản

Source code:
1 ; hello world ASM 2 3 global _start 4 section .text 5 6 _start: 7 ; write(int fd, const void *buf, size_t count) 8 xor eax,eax 9 xor ebx,ebx 10 xor ecx,ecx 11 xor edx,edx 12 mov al,0x4 13 inc bl 14 push 0x000a2164 15 push 0x6c726f57 16 push 0x202c6f6c 17 push 0x6c6548 18 mov ecx,esp 19 mov dl,0xf 20 int 0x80 21 22 ; exit(int status) 23 xor eax,eax 24 xor ebx,ebx 25 mov al,0x1 26 int 0x80
Biên dịch, liên kết và chạy chương trình:
$ nasm -f elf32 -o ch02-helloworld.o ch02-helloworld.asm $ ld -o ch02-helloworld ch02-helloworld.o $ chmod +x ch02-helloworld $ ./ch02-helloworld Hello, World!
Giải thích chi tiết:
  • Dòng 8, 9, 10, 11: Khởi tạo giá trị 0 cho các thanh ghi EAX, EBX, ECX, EDX
  • Dòng 12: AL = System Call Number = 0x4 ⇒ Tra cứu trong unistd_32.h ta được: #define __NR_write 4. Hàm write trong Man Page: write(int fd, const void *buf, size_t count);
  • Dòng 13: BL = 0x1 ⇒ fd = STDOUT (Ngoài ra: 0=STDIN; 2=STDERR)
  • Dòng 14, 15, 16, 17: Đẩy dữ liệu dạng Hexa lên Stack. Dựa theo Little-Endian ta sẽ decode dữ liệu này như sau:
    • $ python >>> a = '000a2164'.decode('hex') >>> b = '6c726f57'.decode('hex') >>> c = '202c6f6c'.decode('hex') >>> d = '6c6548'.decode('hex') >>> final = a + b + c + d >>> final[::-1] # Little-Endian, Reverse bytes 'Hello, World!\n\x00'
  • Vì x86 Assembly chỉ hỗ trợ độ rộng 32-bits nên sẽ phải PUSH 4 lần, mỗi lần tối đa 4 bytes. Ở cuối có ký \n để xuống dòng và ký tự \0 báo hiệu kết thúc chuỗi.
  • Dòng 18: ECX trỏ đến chuỗi đã PUSH lên Stack ⇒ *buf = 'Hello, World!\n\x00'
  • Dòng 19: EDX = 0xF ⇒ count = 0xF
  • Dòng 20: Gọi ngắt bằng lệnh: INT 0x80
  • Dòng 23, 24: Tương tự 8, 9, 10, 11: Khởi tạo EAX, EBX về giá trị = 0
  • Dòng 25: AL = 0x1 ⇒ System Call Number = 1 ⇒ Hàm: #define __NR_exit 1 và được mô tả như sau: void exit(int status);
Tóm lại, ta có thể chia chương trình thành 2 khối thực thi:
  • Khi đó ta sẽ được:
    • write(fd=1, *buf='Hello, World!\n\x00', count=15); exit(status=0);

5. Cấu trúc tệp ELF32 trên Linux

ELF - Executable and Linking Format: Là định dạng tệp thực thi phổ biến trên Linux, nó có thể là các chương trình phần mềm, các thư viện, các drivers hay Linux Kernel Module,.v.v.. Việc hiểu cấu trúc của tệp ELF32 giúp người phân tích có cái nhìn tổng quan, hiểu được đặc tính kỹ thuật của tệp. Xác định được loại tệp cũng như cấu trúc tệp tin là giai đoạn đầu trong bất kỳ quá trình phân tích Binary nào.

5.1. Cấu trúc cơ bản tệp ELF

Cấu trúc tệp ELF được định nghĩa trong header file: /usr/include/elf.h. Về cơ bản nó có ba phần chính: ELF Header, Program Header TableSection Header Table. Trong đó:
ELF Header: Nằm ở đầu tệp ELF, chứa thông tin cơ bản về tệp: Magic, Type, Machine, Entry Point, Start of Program Headers/Section Headers, Number of Program Headers/Section Headers, Size of ELF Header/Program Headers/Section Headers,.v.v..
/* The ELF file header. This appears at the start of every ELF file. */ #define EI_NIDENT (16) typedef struct { unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ Elf32_Half e_type; /* Object file type */ Elf32_Half e_machine; /* Architecture */ Elf32_Word e_version; /* Object file version */ Elf32_Addr e_entry; /* Entry point virtual address */ Elf32_Off e_phoff; /* Program header table file offset */ Elf32_Off e_shoff; /* Section header table file offset */ Elf32_Word e_flags; /* Processor-specific flags */ Elf32_Half e_ehsize; /* ELF header size in bytes */ Elf32_Half e_phentsize; /* Program header table entry size */ Elf32_Half e_phnum; /* Program header table entry count */ Elf32_Half e_shentsize; /* Section header table entry size */ Elf32_Half e_shnum; /* Section header table entry count */ Elf32_Half e_shstrndx; /* Section header string table index */ } Elf32_Ehdr;
Program Header Table: Bảng chứa các Program Header (hay còn được gọi là các Segment Header). Mỗi Segment chứa 0 hoặc nhiều Section
/* Program segment header. */ typedef struct { Elf32_Word p_type; /* Segment type */ Elf32_Off p_offset; /* Segment file offset */ Elf32_Addr p_vaddr; /* Segment virtual address */ Elf32_Addr p_paddr; /* Segment physical address */ Elf32_Word p_filesz; /* Segment size in file */ Elf32_Word p_memsz; /* Segment size in memory */ Elf32_Word p_flags; /* Segment flags */ Elf32_Word p_align; /* Segment alignment */ } Elf32_Phdr;
Section Header Table: Bảng chứa các Section Header, mỗi Section sẽ được quy định có quyền: đọc, ghi hay thực thi và nó lưu: dữ liệu hay code thực thi,.v.v..
/* Section header. */ typedef struct { Elf32_Word sh_name; /* Section name (string tbl index) */ Elf32_Word sh_type; /* Section type */ Elf32_Word sh_flags; /* Section flags */ Elf32_Addr sh_addr; /* Section virtual addr at execution */ Elf32_Off sh_offset; /* Section file offset */ Elf32_Word sh_size; /* Section size in bytes */ Elf32_Word sh_link; /* Link to another section */ Elf32_Word sh_info; /* Additional section information */ Elf32_Word sh_addralign; /* Section alignment */ Elf32_Word sh_entsize; /* Entry size if section holds table */ } Elf32_Shdr;

5.2. Trích xuất Metadata trong ELF32

Phần này giới thiệu một số công cụ nguồn mở, miễn phí và ưu tiên có sẵn trên Linux vì chúng ta đang tập chung phân tích các tệp ELF32:
 
READELF: Công cụ này hiển thị rất nhiều thông tin về một tệp ELF. Một số Option hữu dụng khi làm việc với công cụ này:
  • [-h|--file-header]: Hiển thị ELF header
  • [-l|--program-headers|--segments]: Hiển thị Segment headers (Program header)
  • [-S|--sections|--section-headers]: Hiển thị Section header
  • [-e|--headers]: Kết hợp của: -h -l -S
  • [-s|--symbols|--syms]: Hiển thị Symbol section
  • [-d|--dynamic]: Hiển thị Dynamic section
  • [-a|--all]: Hiển thị tất cả thông tin, là kết hợp của: -h -l -S -s -r -d -V -A -I
  • [-x <number or name>|--hex-dump=<number or name>]: Dump hex của một Section
  • [-p <number or name>|--string-dump=<number or name>]: Dump string của một Section
  • [-W|--wide]: Làm cho output không bị ngắt khi vượt quá 80 ký tự.
 
Demo công cụ ReadELF:
Show ELF header: Chứa các thông tin cơ bản
notion image
Show Program header
notion image
Show Section header: Chú ý section .text được gắn Flag là: X.
notion image
Show Symbol Table: Bảng này cho biết các biến, các hàm mà chương trình sử dụng
notion image
Dump Strings một Section: dùng -p
notion image
Dump một Section: dùng -R hoặc -x đều có thể dump được
notion image
 
XELFViewer: Công cụ này có giao diện GUI dễ dùng, hỗ trợ cả Windows, MacOS và Linux. Tác giả là NTInfo, các công cụ nổi tiếng khác cùng tác giả: Detect It Easy (DiE), XAPKDetector, XVolkolak, XOpcodeCalc, Nauz File Detector(NFD), x64dbg Plugin Manager,.v.v..
ELF Header:
notion image
Section Header:
notion image
Program Header:
notion image
Symbol Table:
notion image