CPU Architecture Overview
Kiến trúc máy tính được sử dụng nhiều nhất là kiến trúc Von Neumann:
Trong sơ đồ trên: CPU gồm ba phần là arithmetic logic unit (ALU), control unit và các thanh ghi. CPU tương tác với bộ nhớ chính (RAM) và các thiết bị nhập xuất.
Control Unit
Nhận các chỉ thị (instruction) từ bộ nhớ chính.
Arithmetic Logic Unit
Thực thi các instruction lấy từ bộ nhớ thông qua control unit. Kết quả thực thi sau đó sẽ được lưu trong các thanh ghi hoặc bộ nhớ.
Registers
Là bộ nhớ của CPU. Các thanh ghi có kích thước nhỏ hơn bộ nhớ chính nhưng có tốc độ thực thi nhanh hơn do tương tác trực tiếp với CPU.
Memory
Khi người dùng thực thi một chương trình thì code và dữ liệu của nó sẽ được nạp lên bộ nhớ chính và sẽ được CPU xử lý bằng một instruction tại một thời điểm.
Registers Overview
Do có kích thước nhỏ nên các thanh ghi cần phải được sử dụng một cách hiệu quả. Để đạt được điều này, các thanh ghi được chia thành các loại sau:
- Instruction pointer
- General purpose register
- Status flag register
- Segment register
Bảng tổng hợp tất cả các thanh ghi
Instruction Pointer | General Registers | Status Registers | Segment Registers |
---|---|---|---|
EIP, RIP | RAX, EAX, AX, AH, AL | EFLAGS | CS |
RBX, EBX, BX, BH, BL | SS | ||
RCX, ECX, CX, CH, CL | DS | ||
RDX, EDX, DX, DH, DL | ES | ||
RBP, EBP, BP | FS | ||
RSP, ESP, SP | GS | ||
RSI, ESI, SI | |||
RDI, EDI, DI | |||
R8-R15 |
The Instruction Pointer (IP)
Là một thanh ghi chứa địa chỉ của instruction tiếp theo sẽ được thực thi bởi CPU. Nó còn được gọi là program counter.
Ban đầu IP là một thanh ghi 16-bit trong các bộ vi xử lý Intel 8086 (x86). Trong các bộ vi xử lý 32-bit, nó là một thanh ghi 32-bit và được gọi là EIP (Extended Instruction Pointer). Còn trong các hệ thống 64-bit thì nó là thanh ghi 64-bit và có tên gọi là RIP (R là register).
General-Purpose Registers
Các thanh ghi đa dụng trong kiến trúc x86 đều có kích thước 32-bit. Đối với các hệ thống 64-bit thì các thanh ghi này có kích thước là 64-bit. Chúng là các thanh ghi được sử dụng bởi CPU để thực thi các chỉ thị, bao gồm các thanh ghi sau:
Accumulator Register (EAX/RAX)
Là thanh ghi lưu kết quả tính toán. Ở các hệ thống 32-bit thì nó là EAX còn ở các hệ thống 64-bit thì nó là RAX. 16-bit cuối của thanh ghi này được đánh địa chỉ AX. Tương tự, 8-bit cao của AX được đánh địa chỉ AH và 8-bit thấp của AX được đánh địa chỉ AL.
Base Register (EBX/RBX)
Thường được sử dụng để lưu base address khi tham chiếu đến một offset. Tương tự với EAX/RAX, nó được đánh địa chỉ như sau: 64-bit RBX, 32-bit EBX, 16-bit BX, 8-bit cao BH và 8-bit thấp BL.
Counter Register (ECX/RCX)
Thường được dùng để đếm các thao tác chẳng hạn như vòng lặp.
Data Register (EDX/RDX)
Thường được dùng trong phép nhân và chia.
Stack Pointer (ESP/RSP)
Trỏ đến đầu stack và còn được sử dụng làm thanh ghi Stack Segment. Phiên bản 32-bit của nó là ESP còn phiên bản 64-bit của nó là RSP. Nó không thể được chia nhỏ nữa như các thanh ghi trên.
Base Pointer (EBP/RBP)
Dùng để truy cập đến các tham số truyền vào bởi stack.
Source Index Register (ESI/RSI)
Thường được dùng cho các thao tác liên quan đến chuỗi. Nó được sử dụng cùng với thanh ghi data segment (DS) như là một offset.
Destination Index Register (EDI/RDI)
Cũng được dùng cho các thao tác liên quan đến chuỗi. Nó được sử dụng cùng với thanh ghi extra segment (ES) như là một offset
R8-R15
Là các thanh ghi tồn tại trong các hệ thống 64-bit. Chúng cũng được đánh địa chỉ cho 32-bit, 16-bit và 8-bit. Ví dụ, với thanh ghi R8, chúng ta có thể dùng R8D làm 32-bit thấp, R8W cho 16-bit thấp, R8B cho 8-bit thấp. Hậu tố D có nghĩa là Double-Word, W có nghĩa là Word và B là Byte.
Status Flag Registers
Thanh ghi này được dùng để lưu các trạng thái phục vụ cho việc thực thi các chỉ thị. Kích thước của nó là 32-bit trong các hệ thống 32-bit và tên gọi là EFLAGS. Còn đối với các hệ thống 64-bit thì kích thước của nó là 64-bit và tên gọi là RFLAGS. Mỗi flag sẽ có một bit dùng để lưu dữ liệu.
Một số flag:
Zero Flag (ZF)
Cho biết kết quả của chỉ thị trước đó đã được thực thi là 0. Ví dụ, nếu có một chỉ thị nào đó làm giá trị của thanh ghi RAX bằng 0 thì flag này sẽ được set thành 1.
Carry Flag (CF)
Cho biết kết quả của chỉ thị quá lớn hoặc quá nhỏ để nhét vào destination. Ví dụ, nếu chúng ta cộng 0xFFFFFFFF
với 0x00000001
và lưu ở trong thanh ghi 64-bit thì sẽ không vừa. Khi đó, flag này sẽ được set thành 1.
Sign Flag (SF)
Cho biết kết quả của một thao tác là số âm hoặc most significant bit được set thành 1. Nếu thỏa một trong hai điều kiện thì flag này sẽ được set thành 1, nếu không sẽ là 0.
Trap Flag (TF)
Cho biết bộ vi xử lý có đang trong chế độ debug hay không. Khi TF được set, CPU sẽ thực thi một instruction tại một thời điểm.
Segment Registers
Có nhiệm vụ chuyển các flat memory space thành các segment để dễ đánh địa chỉ. Có 6 segment register:
- Code segment (CS): trỏ đến code section trong memory.
- Data segment (DS): trỏ đến data section trong memory.
- Stack segment (SS): trỏ đến stack của chương trình ở trong memory.
- Extra segments (ES, FS, GS): trỏ đến các chỗ khác nhau của data section. Các thanh ghi này cùng với DS chia data section thành 4 phần.
Memory Overview
Khi một chương trình được nạp lên bộ nhớ chính thì nó chỉ thấy bộ nhớ trừu tượng mà hệ điều hành cấp phát.
Hình bên dưới là memory layout điển hình của một chương trình.
Có thể thấy, bộ nhớ chính được chia ra làm 4 phần: stack, heap, code và data. Thứ tự của từng section không nhất thiết phải theo hình trên.
Code
Chứa mã nguồn của chương trình. Cụ thể hơn, nó trỏ đến text section trong portable executable file. Section này có các quyền thực thi nên CPU có thể thực thi dữ liệu trong section này khi nó được nạp lên bộ nhớ chính.
Data
Chứa các dữ liệu khởi tạo mà không phải là biến (các hằng số). Nó trỏ đến data section trong portable executable file. Nó thường chứa các biến toàn cục và các giá trị không thay đổi trong suốt quá trình thực thi của chương trình.
Heap
Còn được gọi là dynamic memory, chứa các biến và dữ liệu được tạo ra và phá hủy trong quá trình thực thi của chương trình. Khi một biến được tạo ra, bộ nhớ sẽ được cấp phát cho biến đó trong runtime. Và bộ nhớ cũng sẽ được giải phóng khi biến bị phá hủy.
Stack
Chứa các biến cục bộ, đối số truyền vào chương trình và return address của parent process gọi đến chương trình. Do return address liên quan đến control flow của các chỉ thị CPU nên stack thường bị nhắm đến bởi malware để chiếm đoạt quyền điều khiển.
Stack Layout
CPU sử dụng hai thanh ghi để theo dõi stack: stack pointer (ESP/RSP) và base pointer (EBP/RBP).
Stack Pointer
Trỏ đến đầu stack. Giá trị của thanh ghi này sẽ thay đổi khi có một phần tử được thêm vào stack hoặc bị xóa khỏi stack.
The Base Pointer
Mỗi chương trình đều có một base pointer cố định. Đây là địa chỉ giúp chương trình theo dõi các biến cục bộ và các đối số của nó.
Return Address and Old Base Pointer
Là địa chỉ trỏ đến chỉ thị kế tiếp tính từ vị trí của instruction pointer.
Một kỹ thuật phổ biến để chiếm quyền điều khiển là làm tràn một biến cục bộ để nó ghi đè lên return address bằng một address nào đó tùy ý. Kỹ thuật này còn được gọi là Stack Buffer Overflow.
Giá trị của base pointer trước khi thực thi hàm cũng sẽ được lưu vào stack. Lý do cần lưu giá trị này là vì base pointer sau đó sẽ trỏ đến stack pointer nhằm giúp stack pointer khôi phục giá trị khi kết thúc hàm.
Arguments
Các đối số truyền vào hàm được đẩy vào dưới return address trong stack trước khi hàm được thực thi.
Minh họa:
Function Prologue and Epilogue
Quá trình bắt đầu một hàm (function prologue):
- Khi một hàm được gọi thực thi, các đối số của nó sẽ được đẩy vào stack.
- Sau đó, return address và base pointer cũng sẽ được đẩy vào stack.
- Trỏ base pointer đến stack pointer (tạo một base pointer mới).
- Trong quá trình thực thi, stack pointer sẽ di chuyển để cấp phát bộ nhớ cho các biến cục bộ của hàm.
Quá trình kết thúc một hàm (function epilogue):
- Stack pointer sẽ trỏ đến base pointer hiện tại, vùng nhớ của các biến cục bộ sẽ được giải phóng.
- Base pointer được lưu trong stack sẽ được lấy ra.
- Return address được xóa khỏi stack và được gán cho instruction pointer, cho phép chương trình tiếp tục thực thi sau lời gọi hàm.
Hợp ngữ minh họa:
section .text
global _start
_start:
; Prologue của hàm
push ebp ; Lưu giá trị của base pointer vào stack để có thể khôi phục về sau.
mov ebp, esp ; Trỏ base pointer đến stack pointer.
; Cấp phát không gian cho biến cục bộ, ví dụ: 4 byte
sub esp, 4
; Thực hiện các lệnh trong hàm ở đây
; Epilogue của hàm
mov esp, ebp ; Trỏ stack pointer đến base pointer.
pop ebp ; Lấy giá trị base pointer từ stack.
ret ; Trả về return address.
Success
THM{SMASHED_THE_STACK}
Related
list
from outgoing([[TryHackMe - x86 Architecture Overview]])
sort file.ctime asc