Advertisement

Responsive Advertisement

Intro to x86-64-Tryhackme

 

Task 1  Description and Objectives

Phòng này sẽ xem xét các nguyên thủy cơ bản của lập trình hợp ngữ x86-64 của Intel và sẽ sử dụng các nguyên thủy này để hiểu việc xây dựng các chương trình cơ bản bằng cách sử dụng các vòng lặp, hàm và thủ tục. Các nhiệm vụ gắn liền với phòng này sẽ sử dụng khung kỹ thuật đảo ngược r2, khung này sẽ được cài đặt trong máy gắn với phòng này. Tên người dùng của máy được gắn với tác vụ tiếp theo là tryhackme và mật khẩu là reismyfavl33t. Để truy cập vào máy, hãy SSH vào nó trên cổng 22.

Dưới đây là một số điều cần lưu ý trước khi bắt đầu phòng:

  • 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
Vì kiến trúc là x86-64, các thanh ghi là 64 bit và Intel có danh sách 16 thanh ghi:

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 ghi

  • Chuyể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 rbx

  • Truyề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ừ đâu

  • addq source, destination: đích = đích + nguồn

  • subq source, destination: đích = đích - nguồn

  • imulq source, destination: đích = đích * nguồn

  • salq source, destination: đích = đích << nguồn trong đó << là toán tử dịch chuyển bit sang trái

  • sarq 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 đích

  • andq source, destination: đích = đích AND nguồn

  • orq 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ồn

  • testq 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 jmphướ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

Và điều đó cho thấy giá trị của 4.


Chúng ta biết rằng eax chứa 3 và 3 không lớn hơn 4, vì vậy bước nhảy sẽ không thực hiện. Thay vào đó, nó sẽ chuyển sang hướng dẫn tiếp theo. Để kiểm tra điều này, hãy chạy 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

Làm điều này cho phép chúng ta bỏ qua một vài dòng hướng dẫn đầu tiên, như chúng ta đã thấy khi sử dụng câu lệnh if, nó chỉ chuyển các giá trị đến các đối số cục bộ (lưu ý rằng hằng số được hiển thị bởi $ 0xa đại diện cho giá trị đó là 10 trong hex). Khi quá trình thực thi đạt đến điểm ngắt tại lệnh jmp, hãy chạy 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!


Post a Comment

0 Comments