Task 1 Description and Objectives
- Phòng này sẽ sử dụng cú pháp AT&T. Nói chung, mọi người sử dụng cú pháp AT&T hoặc Cú pháp Intel (sự khác biệt được nêu rõ ở đây).
- Phòng này nhằm mục đích giới thiệu nhẹ nhàng về radare2. Mặc dù chúng không được hiển thị ở đây, radare có rất nhiều tính năng và công cụ mạnh mẽ có thể được tìm thấy tại đây, tại đây và tại đây
- Ngay khi bắt đầu r2, hãy nhớ nhập e asm.syntax = att để đảm bảo rằng bạn đang sử dụng cú pháp AT&T.
- Địa chỉ hiển thị trên hình ảnh trong các tác vụ bên dưới có thể khác với địa chỉ bạn xem khi bạn tháo rời tệp.
Task 2 Introduction
Máy tính thực thi mã máy, được mã hóa dưới dạng byte, để thực hiện các tác vụ trên máy tính. Vì các máy tính khác nhau có các bộ xử lý khác nhau, mã máy được thực thi trên các máy tính này là dành riêng cho bộ xử lý. Trong trường hợp này, chúng ta sẽ xem xét kiến trúc tập lệnh Intel x86-64 thường thấy nhất hiện nay. Mã máy thường được biểu diễn bằng một dạng mã dễ đọc hơn được gọi là mã lắp ráp. Mã máy này thường được tạo ra bởi một trình biên dịch, lấy mã nguồn của một tệp, và sau khi trải qua một số giai đoạn trung gian, tạo ra mã máy có thể được thực thi bởi máy tính. Không đi sâu vào quá nhiều chi tiết, Intel bắt đầu bằng cách xây dựng tập lệnh 16 bit, tiếp theo là 32 bit, sau đó cuối cùng họ tạo ra 64 bit. Tất cả các tập lệnh này đã được tạo để tương thích ngược, vì vậy mã được biên dịch cho kiến trúc 32 bit sẽ chạy trên các máy 64 bit. Như đã đề cập trước đó, trước khi một tệp thực thi được tạo ra, mã nguồn đầu tiên được biên dịch thành assembly (tệp .s), sau đó trình hợp dịch chuyển đổi nó thành một chương trình đối tượng (tệp .o) và các hoạt động với trình liên kết cuối cùng làm cho nó trở thành có thể thực thi được. Cách tốt nhất để thực sự bắt đầu giải thích lắp ráp là đi sâu vào. Chúng tôi sẽ sử dụng radare2 để làm điều này - radare2 là một khuôn khổ cho kỹ thuật đảo ngược và phân tích các tệp nhị phân. Nó có thể được sử dụng để tháo rời các tệp nhị phân (dịch mã máy sang hợp ngữ, thực sự có thể đọc được) và gỡ lỗi các tệp nhị phân đã nói (bằng cách cho phép người dùng thực hiện và xem trạng thái của chương trình). Bước đầu tiên là thực hiện phần giới thiệu chương trình bằng:
./intro
Màn hình hiển thị như sau:
Từ quá trình thực thi, có thể thấy rằng chương trình đang tạo hai biến và chuyển đổi giá trị của chúng. Đã đến lúc để xem nó thực sự đang làm gì!
Chuyển đến thư mục giới thiệu trên máy ảo và chạy lệnh:
r2 -d intro
Thao tác này sẽ mở tệp nhị phân trong chế độ gỡ lỗi. Khi mã nhị phân được mở, một trong những điều đầu tiên cần làm là yêu cầu r2 phân tích chương trình và điều này có thể được thực hiện bằng cách nhập:
aa
Lệnh phân tích nào là phổ biến nhất. Nó phân tích tất cả các ký hiệu và điểm vào trong tệp thực thi. bằng cách chạy
e asm.syntax=att
để đặt cú pháp gỡ bỏ thành AT&T.
Phân tích trong trường hợp này liên quan đến việc trích xuất tên chức năng, thông tin điều khiển luồng và nhiều hơn nữa! Các lệnh r2 thường dựa trên một ký tự duy nhất, vì vậy có thể dễ dàng lấy thêm thông tin về các lệnh. Để được trợ giúp chung, hãy chạy:
?
Để biết thêm thông tin cụ thể, ví dụ: về phân tích, hãy chạy
a?
Sau khi phân tích xong, bạn sẽ muốn biết bắt đầu phân tích từ đâu - hầu hết các chương trình đều có điểm đầu vào được xác định là chính. Để tìm danh sách các hàm đã chạy:
afl
Như đã thấy ở đây, thực sự có một chức năng ở chính. Hãy kiểm tra mã lắp ráp tại chính bằng cách chạy lệnh
pdf @main
Trong đó pdf có nghĩa là chức năng phân tích mã. Làm như vậy sẽ cho chúng ta cái nhìn chi tiết
Như chúng ta có thể thấy ở trên, các giá trị trên cột bên trái hoàn chỉnh là địa chỉ bộ nhớ của các lệnh và chúng thường được lưu trữ trong một cấu trúc được gọi là ngăn xếp (mà chúng ta sẽ nói đến sau). Cột giữa chứa các hướng dẫn được mã hóa theo byte (thường là mã máy) và cột cuối cùng thực sự chứa các hướng dẫn mà con người có thể đọc được.
Cốt lõi của hợp ngữ liên quan đến việc sử dụng các thanh ghi để thực hiện những việc sau:
- Truyền dữ liệu giữa bộ nhớ và thanh ghi, và ngược lại
- Thực hiện các phép toán số học trên thanh ghi và dữ liệu
- Chuyển quyền kiểm soát sang các phần khác của chương trình
64 bit | 32 bit |
%rax | %eax |
%rbx | %ebx |
%rcx | %ecx |
%rdx | %edx |
%rsi | %esi |
%rdi | %edi |
%rsp | %esp |
%rbp | %ebp |
%r8 | %r8d |
%r9 | %r9d |
%r10 | %r10d |
%r11 | %r11d |
%r12 | %r12d |
%r13 | %r13d |
%r14 | %r14d |
%r15 | %r15d |
Mặc dù các thanh ghi là 64 bit, nghĩa là chúng có thể chứa tới 64 bit dữ liệu, các phần khác của thanh ghi cũng có thể được tham chiếu. Trong trường hợp này, các thanh ghi cũng có thể được tham chiếu dưới dạng các giá trị 32 bit như được hiển thị. Những gì không được hiển thị là các thanh ghi có thể được tham chiếu dưới dạng 16 bit và 8 bit (4 bit cao hơn và 4 bit thấp hơn). 6 thanh ghi đầu tiên được gọi là thanh ghi mục đích chung. %Rsp là con trỏ ngăn xếp và nó trỏ đến đầu ngăn xếp chứa địa chỉ bộ nhớ gần đây nhất. Ngăn xếp là một cấu trúc dữ liệu quản lý bộ nhớ cho các chương trình. % rbp là một con trỏ khung và trỏ đến khung của hàm hiện đang được thực thi - mọi hàm được thực thi trong một khung mới. Để di chuyển dữ liệu bằng cách sử dụng thanh ghi, hướng dẫn sau được sử dụng:
movq source, destination
Điều này liên quan đến:
Chuyển các hằng số (được đặt tiền tố bằng cách sử dụng toán tử $), ví dụ:
movq $3 rax
sẽ di chuyển hằng số 3 vào thanh ghiChuyển các giá trị từ một thanh ghi, ví dụ:
movq %rax %rbx
liên quan đến việc chuyển giá trị từ rax sang rbxTruyền các giá trị từ bộ nhớ được hiển thị bằng cách đặt các thanh ghi bên trong dấu ngoặc nhọn, ví dụ:
movq %rax (%rbx)
có nghĩa là di chuyển giá trị được lưu trữ trong% rax đến vị trí bộ nhớ được đại diện bởi% rbx.
Chữ cái cuối cùng của lệnh mov thể hiện kích thước của dữ liệu:
Intel Data Type | Suffix | Size(bytes) |
Byte | b | 1 |
Word | w | 2 |
Double Word | l | 4 |
Quad Word | q | 8 |
Single Precision | s | 4 |
Double Precision | l | 8 |
Khi xử lý thao tác bộ nhớ bằng cách sử dụng thanh ghi, có những trường hợp khác cần được xem xét:
(Rb, Ri) = MemoryLocation[Rb + Ri]
D(Rb, Ri) = MemoryLocation[Rb + Ri + D]
(Rb, Ri, S) = MemoryLocation(Rb + S * Ri]
D(Rb, Ri, S) = MemoryLocation[Rb + S * Ri + D]
Một số thông tin quan trọng khác:
leaq source, destination
: đặt địa chỉ đích được biểu thị bằng biểu thức trong nguồn từ đâuaddq source, destination
: đích = đích + nguồnsubq source, destination
: đích = đích - nguồnimulq source, destination
: đích = đích * nguồnsalq source, destination
: đích = đích << nguồn trong đó << là toán tử dịch chuyển bit sang tráisarq source, destination
: đích = đích >> nguồn trong đó >> là toán tử dịch chuyển bit sang phải.xorq source, destination
: đích = nguồn XOR đíchandq source, destination
: đích = đích AND nguồnorq source, destination
: đích = đích OR nguồn
Trước khi hiểu cách các chương trình hoạt động, điều quan trọng là phải hiểu các thanh ghi, thao tác trên bộ nhớ và một số hướng dẫn cơ bản. Các phần tiếp theo sẽ sử dụng radare2 nhiều hơn.
Task 3 If Statements
Định dạng chung của câu lệnh if là
if(condition)
{
do-stuff-here
}
else if(condition) //this is an optional condition
{
do-stuff-here
}
else {
do-stuff-here
}
Câu lệnh if sử dụng 3 hướng dẫn quan trọng trong assembly:
cmpq source2, source1
: nó giống như tính toán a-b mà không cần thiết lập nguồntestq source2, source1
: nó giống như tính toán a & b mà không cần thiết lập nguồn
Hướng dẫn lệnh nhảy được sử dụng để chuyển quyền điều khiển sang các lệnh khác nhau và có các loại bước nhảy khác nhau:
Jump Type | Description |
jmp | Vô điều kiện |
je | Bằng / Không |
jne | Không bằng / Không bằng 0 |
js | Phủ định |
jns | Không âm |
jg | Lớn hơn |
jge | Lớn hơn hoặc bằng |
jl | Nhỏ hơn |
jle | Nhỏ hơn hoặc bằng |
ja | Dương (có dấu) |
jb | Am(có dấu) |
2 giá trị cuối cùng của bảng tham chiếu đến các số nguyên không dấu. Số nguyên không dấu không được âm trong khi số nguyên có dấu đại diện cho cả giá trị âm và dương. Khi máy tính cần phân biệt giữa chúng, nó sẽ sử dụng các phương pháp khác nhau để diễn giải các giá trị này. Đối với số nguyên có dấu, nó sử dụng một cái gì đó được gọi là biểu diễn phần bù của hai và đối với số nguyên không dấu, nó sử dụng các phép tính nhị phân thông thường.
Task 4 If Statements Continued
Chuyển đến thư mục if-statement và Bắt đầu r2 với r2 -d if1
Và chạy các lệnh sau:
aaa
afl
pdf @main
Điều này phân tích chương trình, liệt kê các chức năng và tháo rời chức năng chính.
Sau đó,mình sẽ bắt đầu bằng cách thiết lập điểm ngắt tại jge
và tại jmp
hướng dẫn bằng cách sử dụng lệnh:
db 0x55ae52836612
(đó là địa chỉ hex của jge
)
db 0x55ae52836618
(đó là địa chỉ hex của jmp
)
Mình đã thêm các điểm ngắt để dừng việc thực thi chương trình tại các điểm đó để mình có thể xem trạng thái của chương trình. Làm như vậy sẽ hiển thị như sau:
Bây giờ chạy dc
để bắt đầu thực hiện chương trình và chương trình sẽ bắt đầu thực hiện và dừng lại tại điểm ngắt. Hãy kiểm tra những gì đã xảy ra trước khi đạt được điểm dừng:
- 2 dòng đầu tiên là về việc đẩy con trỏ khung lên ngăn xếp và lưu nó (đây là về cách các hàm được gọi và sẽ được xem xét sau)
- 3 dòng tiếp theo là về việc gán giá trị 3 và 4 cho các đối số / biến cục bộ var_8h và var_4h. Sau đó, nó lưu trữ giá trị trong var_8h trong thanh ghi% eax.
- Lệnh
cmpl
so sánh giá trị của eax với giá trị của đối số var_8h
Để xem giá trị của các thanh ghi, hãy nhập: dr
Chúng ta có thể thấy rằng giá trị của rax, là phiên bản 64 bit của eax chứa 3. Chúng ta thấy rằng lệnh jge đang nhảy dựa trên việc giá trị của eax có lớn hơn var_4h hay không. Để xem có gì trong var_4h, chúng ta có thể thấy rằng ở đầu hàm main, nó cho chúng ta biết vị trí của var_4h. Chạy lệnh: px @rbp-0x4
ds
lệnh tìm kiếm/chuyển sang lệnh tiếp theo.
Rip (là con trỏ lệnh hiện tại) cho thấy rằng nó chuyển sang lệnh tiếp theo - điều này cho thấy chúng tôi đã đúng. Sau đó, lệnh hiện tại sẽ thêm 5 vào var_8h, đây là một đối số cục bộ. Để thấy rằng điều này thực sự xảy ra, trước tiên hãy kiểm tra giá trị của var_8h, chạy ds
và kiểm tra lại giá trị. Điều này sẽ hiển thị nó tăng lên 5.
Lưu ý rằng vì chúng tôi đang kiểm tra địa chỉ chính xác, chúng tôi chỉ cần kiểm tra đến 0 bù đắp. Giá trị được lưu trữ trong bộ nhớ được lưu trữ dưới dạng hex.
Lệnh tiếp theo là một bước nhảy vô điều kiện và nó chỉ nhảy để xóa sổ đăng ký eax. Lệnh popq
có liên quan đến việc bật một giá trị của ngăn xếp và đọc nó, và lệnh trả về đặt giá trị được bật này thành con trỏ lệnh hiện tại. Trong trường hợp này, nó cho thấy việc thực thi chương trình đã được hoàn thành. Để hiểu rõ hơn về cách hoạt động của câu lệnh if, bạn có thể kiểm tra tệp C tương ứng trong cùng một thư mục.
Task 5 Loops
Thông thường người ta sử dụng hai loại vòng lặp: vòng lặp for và vòng lặp while. Định dạng chung của vòng lặp while là:
while(condition){
Do-stuff-here
Change value used in condition
}
Định dạng chung của vòng lặp for là
for(initialise value: condition; change value used in condition){
do-stuff-here
}
Hãy bắt đầu tìm kiếm các vòng lặp bằng cách nhập thư mục vòng lặp, chạy r2 với tệp các vòng lặp 1. Sau đó, hãy phân tích mọi thứ, liệt kê các chức năng và tháo rời chức năng chính
Hãy bắt đầu bằng cách đặt điểm ngắt tại lệnh jmp bằng lệnh: db address-of-instruction
ds
để chuyển sang hướng dẫn tiếp theo. Vì đây là bước nhảy không điều kiện nên nó sẽ chuyển sang lệnh cmpl.
Ở đây, lệnh cmpl đang cố gắng so sánh những gì có trong đối số cục bộ var_ch với giá trị 8. Để xem những gì trong var_ch, hãy kiểm tra phần bắt đầu của hàm đã tháo rời và kiểm tra bộ nhớ. Trong trường hợp này, nó là rbp-0xc
Và cho thấy rằng nó chứa 4. Lệnh tiếp theo là một jle sẽ kiểm tra xem giá trị là var-ch nhỏ hơn hoặc bằng 8. Vì 4 nhỏ hơn 8, nó sẽ chuyển đến lệnh addl.
Lệnh bổ sung sẽ thêm 2 vào giá trị của var-ch và tiếp tục chuyển đến lệnh cmplins. Vì 2 đã được thêm vào var_ch, var_ch bây giờ sẽ chứa 6 mà vẫn nhỏ hơn 8 và nó sẽ nhảy trở lại lệnh bổ sung. Điều này có thể được nhìn thấy bằng cách tiếp tục thực hiện bằng cách sử dụng dsstatement. Chúng ta biết đây là một vòng lặp vì lệnh addlinstruction đang được thực thi nhiều lần và điều này kết hợp với việc so sánh giá trị của var_ch với 8. Vì vậy, chúng ta có thể suy ra cấu trúc của vòng lặp là be
while(var_ch < 8){
var_ch = var_ch + 2
}
Một cách nhanh hơn để kiểm tra vòng lặp là thêm điểm ngắt vào cmplinstruction và chạy dc. Vì đây là một vòng lặp nên chương trình sẽ luôn ngắt ở lệnh cmplinstruction (vì lệnh này kiểm tra điều kiện trước khi thực hiện những gì bên trong vòng lặp). Bạn có thể kiểm tra tệp loop1.c để xem cấu trúc của vòng lặp!
0 Comments