Binary 102: Linux 32-bit Assembly
Kiến thức cơ bản về x86 Assembly và cấu trúc tệp tin ELF32 trên Linux.
1. Cú pháp Intel và AT&T
Khi làm việc với Assembly trên Linux thì chúng ta bắt gặp nhiều nhất là hai dạng cú pháp: Intel và AT&T. Có nhiều công cụ 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ả hai 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 Intel và AT&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> <source>,<dest>
Cú pháp Intel
<instruction> <dest>,<source>
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:
| Name | Intel | AT&T |
|---|---|---|
| Comments | ; |
// |
| Instructions | Untagged add |
Tagged with operand sizes: addq |
| Registers | eax, ebx, etc. |
%eax%, %ebx%, etc. |
| Immediates | 0x100 |
$0x100 |
| Indirect | [eax] |
(%eax) |
| General indirect | [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-Endian và Little-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
Tham khảo: https://en.wikipedia.org/wiki/Endianness
Để giải thích ngắn gọn và dễ hiểu về Big-Endian và Little-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ụ:
Byte có trọng số lớn nhất sẽ nằm ngoài cùng bên trái ->
0x0AByte có trọng số nhỏ nhất sẽ nằm ngoài cùng bên phải ->
0x0DTheo 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Đị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-Endian và Little-Endian:
Big-Endianquy đị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-Endianquy đị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) sẽ ở 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
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
Bao 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):
Tham khảo: https://en.wikibooks.org/wiki/X86_Assembly/X86_Architecture
Bảng tổng hợp các cờ và vị trí:
Tham khảo: https://en.wikibooks.org/wiki/X86_Assembly/X86_Architecture
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 ghiESPcũ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 ghiESPcũng sẽ được tăng lên.
Các lệnh Logics - Các phép toán thao tác bits
AND: Kết quả củaANDbằng 1 nếu như hai bit là 1, ngược lại bằng 0OR: Kết quả của phépORbằng 0 nếu như hai bit là 0, ngược lại bằng 1NOT: Phép phủ định, cho kết quả ngược lạiXOR: 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,ALLệnh
DIV: Toán hạng đích = Toán hạng đích / Toán hạng nguồn. Tương tự nhưMULnhưng là phép chia.
Các lệnh tăng - giảm giá trị
Lệnh
INCvàDEC: 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)
Một số 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
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 (system calls) 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:
Tham khảo: https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#Via_interrupt
Ta có "công thức" cần nhớ:
x86 Assembly thực hiện System Call thông qua lệnh:
INT 0x80gọi làNgắt 0x80Thanh ghi
EAX/AXsẽ lưuSystem Call NumbervàResultcủa System Call.Các tham số theo thứ tự sau:
EBX,ECX,EDX,ESI,EDI,EBPTra cứu các
System Call Numbertại:unistd_32.hTra cứu các API bằng
Man Pagecủa Linux
4.2. Phân tích một chương trình x86 Assembly đơn giản
Bây giờ hãy thực hành phân tích một chương trình x86 Assembly đơn giản, trước tiên hãy chép đoạn mã dưới đây vào một text editor bất kỳ:
1 ; hello world ASM - ch02-helloworld.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
Lưu lại với tên tùy ý với đuôi .asm sau đó thực hiện biên dịch và liên kết chương trình bằng command sau:
$ 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 chương trình
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: Gán
AL = 0x4. Đây chính làSystem Call Numbercủa hàmwriteđược định nghĩa trong file headerunistd_32.h(#define __NR_write 4). Tra cứu hàm này trong Man Page ta được:write(int fd, const void *buf, size_t count);Dòng 13: Gán
BL = 0x1. Nghĩa là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-Endianta 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
PUSH4 lần, mỗi lần tối đa 4 bytes. Ở cuối có ký\nđể xuống dòng và ký tự\0báo hiệu kết thúc chuỗi.Dòng 18:
ECXtrỏ đến chuỗi đãPUSHlên Stack. Nghĩa là*buf = 'Hello, World!\n\x00'Dòng 19:
EDX = 0xF. Nghĩa làcount = 0xFDòng 20: Gọi ngắt bằng lệnh:
INT 0x80Dòng 23, 24: Tương tự 8, 9, 10, 11: Khởi tạo
EAX,EBXvề giá trị bằng0Dòng 25:
AL = 0x1nghĩa làSystem Call Number = 1tương tự trên, đây là hàmexit(#define __NR_exit 1) và được mô tả như sau trong Man Page:void exit(int status);
Tóm lại, chương trình có hai khối chính
Khối đầu thực hiện hàm
writevà khối sau thực hiện hàmexit:

Tham khảo: https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md
Khi đó ta có thể viết lại đơn giản chương trình thành như sau:
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 Table và Section Header Table.
Tham khảo: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
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ụ dòng lệnh (CLI), có sẵn trên Linux, hiển thị rất nhiều thông tin về một tệp ELF
Một số option hữu ích
[-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ự.
Show ELF header: Chứa các thông tin cơ bản

Show Program header

Show Section header: Chú ý section .text được gắn Flag là: X

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

Dump Strings một Section: dùng -p

Dump một Section: dùng -R hoặc -x đều có thể dump được

XELFViewer: Công cụ giao diện đồ họa (GUI), hỗ trợ đa nền tảng (Windows, Linux, macOS), dễ sử dụng.
Thông tin thêm về công cụ
Tác giả của XELFViewer là NTInfo, cũng là tác giả nhiều công cụ nổi tiếng khác như: Detect It Easy (DiE), XAPKDetector, XVolkolak, XOpcodeCalc, Nauz File Detector(NFD), x64dbg Plugin Manager,.v.v..
ELF Header

Section Header

Program Header

Symbol Table
